diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 00000000..de564e58 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Report a bug for this project + +--- + +**README and remove me** +For community support and other discussion, you are welcome to visit and stay with us @ Freenode, #mailcow +Answering can take a few seconds up to many hours, please be patient. +Commercial support, including a ticket system, can be found @ https://www.servercow.de/mailcow#support - we are also available via Telegram. \o/ + +**Describe the bug, try to make it reproducible** +A clear and concise description of what the bug is. How can it be reproduced? +If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI. + +**System information and quick debugging** +General logs: +- Please take a look at the [documentation](https://mailcow.github.io/mailcow-dockerized-docs/debug-logs/). + +Further information (where applicable): + - Your OS (is Apparmor or SELinux active?) + - Your virtualization technology (KVM/QEMU, Xen, VMware, VirtualBox etc.) + - Your server/VM specifications (Memory, CPU Cores) + - Don't try to run mailcow on a Synology or QNAP NAS, do you? + - Docker and Docker Compose versions + - Output of `git diff origin/master`, any other changes to the code? + - All third-party firewalls and custom iptables rules are unsupported. Please check the Docker docs about how to use Docker with your own ruleset. Nevertheless, iptabels output can help _us_ to help _you_: `iptables -L -vn`, `ip6tables -L -vn`, `iptables -L -vn -t nat` and `ip6tables -L -vn -t nat ` + - Reverse proxy? If you think this problem is related to your reverse proxy, please post your configuration. + - Browser (if it's a Web UI issue) - please clean your browser cache and try again, problem persists? + - Check `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network) and `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @1.1.1.1` - output? Timeout? diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 00000000..860ab66b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here or remove this section diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..24db8c03 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - enhancement +# Label to use when marking an issue as stale +staleLabel: dunno +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.gitignore b/.gitignore index e535c710..5fd3c0f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,20 @@ rebuild-images.sh data/conf/sogo/sieve.creds +data/conf/phpfpm/sogo-sso/sogo-sso.pass data/conf/dovecot/dovecot-master.passwd +data/conf/dovecot/dovecot-master.userdb mailcow.conf mailcow.conf_backup data/conf/nginx/*.active data/conf/postfix/sql +data/conf/postfix/allow_mailcow_local.regexp data/conf/dovecot/sql data/conf/nextcloud-*.bak data/web/inc/vars.local.inc.php data/assets/ssl/* .vscode/* data/web/.well-known/acme-challenge -data/web/nextcloud/ +data/web/nextcloud*/ data/conf/rspamd/local.d/* data/conf/rspamd/override.d/* !data/conf/nginx/dynmaps.conf @@ -20,4 +23,14 @@ data/conf/rspamd/override.d/* data/conf/nginx/*.conf data/conf/nginx/*.custom data/conf/nginx/*.bak +data/conf/dovecot/acl_anyone +data/conf/dovecot/mail_plugins* +data/conf/dovecot/sogo-sso.conf data/conf/dovecot/extra.conf +data/conf/rspamd/custom/* +data/conf/portainer/ +data/gitea/ +data/gogs/ +data/conf/sogo/plist_ldap +.github/ +docker-compose.override.yml diff --git a/README.md b/README.md index 42b2a3f1..ec500d9c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ # mailcow: dockerized - ๐ฎ + ๐ = ๐ -[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JWBSYHF4SMC68) +## Want to support mailcow? -**mailcow Bitcoin donations:** 1E5rgzgA1sS3QH7r1ToWxRC3GEavfsGMrx +Donate via **PayPal** [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JWBSYHF4SMC68) or via **Liberapay** [](https://liberapay.com/mailcow) + +Or just spread the word: moo. + +## Info and documentation Please see [the official documentation](https://mailcow.github.io/mailcow-dockerized-docs/) for instructions. diff --git a/data/Dockerfiles/acme/Dockerfile b/data/Dockerfiles/acme/Dockerfile index ce3fc171..a17064fe 100644 --- a/data/Dockerfiles/acme/Dockerfile +++ b/data/Dockerfiles/acme/Dockerfile @@ -1,10 +1,9 @@ -FROM alpine:3.6 +FROM alpine:3.9 LABEL maintainer "Andre Peters <andre.peters@servercow.de>" RUN apk add --update --no-cache \ bash \ - acme-client \ curl \ openssl \ bind-tools \ @@ -12,7 +11,10 @@ RUN apk add --update --no-cache \ mariadb-client \ redis \ tini \ - tzdata + tzdata \ + py-pip \ + && pip install --upgrade pip \ + && pip install acme-tiny COPY docker-entrypoint.sh /srv/docker-entrypoint.sh COPY expand6.sh /srv/expand6.sh diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index c9a91dfa..e79ef977 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -5,6 +5,16 @@ exec 5>&1 # Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6 source /srv/expand6.sh +# Skipping IP check when we like to live dangerously +if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_IP_CHECK=y +fi + +# Skipping HTTP check when we like to live dangerously +if [[ "${SKIP_HTTP_VERIFICATION}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_HTTP_VERIFICATION=y +fi + log_f() { if [[ ${2} == "no_nl" ]]; then echo -n "$(date) - ${1}" @@ -13,8 +23,12 @@ log_f() { elif [[ ${2} != "redis_only" ]]; then echo "$(date) - ${1}" fi - redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ - tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null + if [[ ${3} == "b64" ]]; then + redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${1}")\"}" > /dev/null + else + redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ + tr '%&;$"[]{}-\r\n' ' ')\"}" > /dev/null + fi } if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then @@ -32,12 +46,34 @@ log_f "OK" no_date ACME_BASE=/var/lib/acme SSL_EXAMPLE=/var/lib/ssl-example -mkdir -p ${ACME_BASE}/acme/private +mkdir -p ${ACME_BASE}/acme -restart_containers(){ +# Migrate +[[ -f ${ACME_BASE}/acme/private/privkey.pem ]] && mv ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/key.pem +[[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/account.pem + +reload_configurations(){ + # Reading container IDs + # Wrapping as array to ensure trimmed content when calling $NGINX etc. + local NGINX=($(curl --silent --insecure https://dockerapi/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow")) | .id' | tr "\n" " ")) + local DOVECOT=($(curl --silent --insecure https://dockerapi/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("dovecot-mailcow")) | .id' | tr "\n" " ")) + local POSTFIX=($(curl --silent --insecure https://dockerapi/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("postfix-mailcow")) | .id' | tr "\n" " ")) + # Reloading + echo "Reloading Nginx..." + NGINX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${NGINX}/exec -d '{"cmd":"reload", "task":"nginx"}' --silent -H 'Content-type: application/json' | jq -r .type) + [[ ${NGINX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Nginx, restarting container..."; restart_container ${NGINX} ; } + echo "Reloading Dovecot..." + DOVECOT_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${DOVECOT}/exec -d '{"cmd":"reload", "task":"dovecot"}' --silent -H 'Content-type: application/json' | jq -r .type) + [[ ${DOVECOT_RELOAD_RET} != 'success' ]] && { echo "Could not reload Dovecot, restarting container..."; restart_container ${DOVECOT} ; } + echo "Reloading Postfix..." + POSTFIX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${POSTFIX}/exec -d '{"cmd":"reload", "task":"postfix"}' --silent -H 'Content-type: application/json' | jq -r .type) + [[ ${POSTFIX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Postfix, restarting container..."; restart_container ${POSTFIX} ; } +} + +restart_container(){ for container in $*; do log_f "Restarting ${container}..." no_nl - C_REST_OUT=$(curl -X POST http://dockerapi:8080/containers/${container}/restart | jq -r '.msg') + C_REST_OUT=$(curl -X POST --insecure https://dockerapi/containers/${container}/restart | jq -r '.msg') log_f "${C_REST_OUT}" no_date done } @@ -90,28 +126,37 @@ get_ipv6(){ echo ${IPV6} } +verify_challenge_path(){ + # verify_challenge_path URL 4|6 + RAND_FILE=${RANDOM}${RANDOM}${RANDOM} + touch /var/www/acme/${RAND_FILE} + if [[ ${SKIP_HTTP_VERIFICATION} == "y" ]]; then + echo '(skipping check, returning 0)' + return 0 + elif [[ "$(curl -${2} http://${1}/.well-known/acme-challenge/${RAND_FILE} --write-out %{http_code} --silent --output /dev/null)" =~ ^(2|3) ]]; then + rm /var/www/acme/${RAND_FILE} + return 0 + else + rm /var/www/acme/${RAND_FILE} + return 1 + fi +} + [[ ! -f ${ACME_BASE}/dhparams.pem ]] && cp ${SSL_EXAMPLE}/dhparams.pem ${ACME_BASE}/dhparams.pem if [[ -f ${ACME_BASE}/cert.pem ]] && [[ -f ${ACME_BASE}/key.pem ]]; then ISSUER=$(openssl x509 -in ${ACME_BASE}/cert.pem -noout -issuer) - if [[ ${ISSUER} != *"Let's Encrypt"* && ${ISSUER} != *"mailcow"* ]]; then + if [[ ${ISSUER} != *"Let's Encrypt"* && ${ISSUER} != *"mailcow"* && ${ISSUER} != *"Fake LE Intermediate"* ]]; then log_f "Found certificate with issuer other than mailcow snake-oil CA and Let's Encrypt, skipping ACME client..." sleep 3650d exec $(readlink -f "$0") - else - declare -a SAN_ARRAY_NOW - SAN_NAMES=$(openssl x509 -noout -text -in ${ACME_BASE}/cert.pem | awk '/X509v3 Subject Alternative Name/ {getline;gsub(/ /, "", $0); print}' | tr -d "DNS:") - if [[ ! -z ${SAN_NAMES} ]]; then - IFS=',' read -a SAN_ARRAY_NOW <<< ${SAN_NAMES} - log_f "Found Let's Encrypt or mailcow snake-oil CA issued certificate with SANs: ${SAN_ARRAY_NOW[*]}" - fi fi else - if [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then - if verify_hash_match ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/acme/private/privkey.pem; then + if [[ -f ${ACME_BASE}/acme/cert.pem ]] && [[ -f ${ACME_BASE}/acme/key.pem ]]; then + if verify_hash_match ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/key.pem; then log_f "Restoring previous acme certificate and restarting script..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem + cp ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/cert.pem + cp ${ACME_BASE}/acme/key.pem ${ACME_BASE}/key.pem # Restarting with env var set to trigger a restart, exec env TRIGGER_RESTART=1 $(readlink -f "$0") fi @@ -123,25 +168,80 @@ else exec env TRIGGER_RESTART=1 $(readlink -f "$0") fi fi +chmod 600 ${ACME_BASE}/key.pem -log_f "Waiting for database... " -while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do +log_f "Waiting for database... " no_nl +while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do sleep 2 done +log_f "OK" no_date + +log_f "Waiting for Nginx... " no_nl +until $(curl --output /dev/null --silent --head --fail http://nginx:8081); do + sleep 2 +done +log_f "OK" no_date + +# Waiting for domain table +log_f "Waiting for domain table... " no_nl +while [[ -z ${DOMAIN_TABLE} ]]; do + curl --silent http://nginx/ >/dev/null 2>&1 + DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs) + [[ -z ${DOMAIN_TABLE} ]] && sleep 10 +done +log_f "OK" no_date + log_f "Initializing, please wait... " - while true; do - if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - SKIP_IP_CHECK=y + + # Re-using previous acme-mailcow account and domain keys + if [[ ! -f ${ACME_BASE}/acme/key.pem ]]; then + log_f "Generating missing domain private key..." + openssl genrsa 4096 > ${ACME_BASE}/acme/key.pem + else + log_f "Using existing domain key ${ACME_BASE}/acme/key.pem" fi + if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then + log_f "Generating missing Lets Encrypt account key..." + openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem + else + log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem" + fi + + chmod 600 ${ACME_BASE}/acme/key.pem + chmod 600 ${ACME_BASE}/acme/account.pem + + # Cleaning up and init validation arrays unset SQL_DOMAIN_ARR unset VALIDATED_CONFIG_DOMAINS unset ADDITIONAL_VALIDATED_SAN + unset ADDITIONAL_WC_ARR + unset ADDITIONAL_SAN_ARR + unset SAN_CHANGE + unset SAN_ARRAY_NOW + unset ORPHANED_SAN + unset ADDED_SAN + SAN_CHANGE=0 + declare -a SAN_ARRAY_NOW + declare -a ORPHANED_SAN + declare -a ADDED_SAN declare -a SQL_DOMAIN_ARR declare -a VALIDATED_CONFIG_DOMAINS declare -a ADDITIONAL_VALIDATED_SAN - IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}" + declare -a ADDITIONAL_WC_ARR + declare -a ADDITIONAL_SAN_ARR + IFS=',' read -r -a TMP_ARR <<< "${ADDITIONAL_SAN}" + for i in "${TMP_ARR[@]}" ; do + if [[ "$i" =~ \.\*$ ]]; then + ADDITIONAL_WC_ARR+=(${i::-2}) + else + ADDITIONAL_SAN_ARR+=($i) + fi + done + ADDITIONAL_WC_ARR+=('autodiscover') + + # Start IP detection log_f "Detecting IP addresses... " no_nl IPV4=$(get_ipv4) IPV6=$(get_ipv6) @@ -160,73 +260,50 @@ while true; do fi fi - # Container ids may have changed - CONTAINERS_RESTART=($(curl --silent http://dockerapi:8080/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow") or contains("postfix-mailcow") or contains("dovecot-mailcow")) | .id' | tr "\n" " ")) - - log_f "Waiting for domain table... " no_nl - while [[ -z ${DOMAIN_TABLE} ]]; do - curl --silent http://nginx/ >/dev/null 2>&1 - DOMAIN_TABLE=$(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs) - [[ -z ${DOMAIN_TABLE} ]] && sleep 10 - done - log_f "OK" no_date - + ######################################### + # IP and webroot challenge verification # while read domains; do SQL_DOMAIN_ARR+=("${domains}") - done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 UNION SELECT alias_domain FROM alias_domain" -Bs) + done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0" -Bs) for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do - A_CONFIG=$(dig A autoconfig.${SQL_DOMAIN} +short | tail -n 1) - AAAA_CONFIG=$(dig AAAA autoconfig.${SQL_DOMAIN} +short | tail -n 1) - # Check if CNAME without v6 enabled target - if [[ ! -z ${AAAA_CONFIG} ]] && [[ -z $(echo ${AAAA_CONFIG} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then - AAAA_CONFIG= - fi - if [[ ! -z ${AAAA_CONFIG} ]]; then - log_f "Found AAAA record for autoconfig.${SQL_DOMAIN}: ${AAAA_CONFIG} - skipping A record check" - if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_CONFIG}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed AAAA record autoconfig.${SQL_DOMAIN}" - VALIDATED_CONFIG_DOMAINS+=("autoconfig.${SQL_DOMAIN}") - else - log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname autoconfig.${SQL_DOMAIN} ($(expand ${AAAA_CONFIG}))" + for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do + if [[ "${SUBDOMAIN}.${SQL_DOMAIN}" != "${MAILCOW_HOSTNAME}" ]]; then + A_SUBDOMAIN=$(dig A ${SUBDOMAIN}.${SQL_DOMAIN} +short | tail -n 1) + AAAA_SUBDOMAIN=$(dig AAAA ${SUBDOMAIN}.${SQL_DOMAIN} +short | tail -n 1) + # Check if CNAME without v6 enabled target + if [[ ! -z ${AAAA_SUBDOMAIN} ]] && [[ -z $(echo ${AAAA_SUBDOMAIN} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then + AAAA_SUBDOMAIN= + fi + if [[ ! -z ${AAAA_SUBDOMAIN} ]]; then + log_f "Found AAAA record for ${SUBDOMAIN}.${SQL_DOMAIN}: ${AAAA_SUBDOMAIN} - skipping A record check" + if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_SUBDOMAIN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then + if verify_challenge_path "${SUBDOMAIN}.${SQL_DOMAIN}" 6; then + log_f "Confirmed AAAA record ${AAAA_SUBDOMAIN}" + VALIDATED_CONFIG_DOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}") + else + log_f "Confirmed AAAA record ${AAAA_SUBDOMAIN}, but HTTP validation failed" + fi + else + log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${SUBDOMAIN}.${SQL_DOMAIN} ($(expand ${AAAA_SUBDOMAIN}))" + fi + elif [[ ! -z ${A_SUBDOMAIN} ]]; then + log_f "Found A record for ${SUBDOMAIN}.${SQL_DOMAIN}: ${A_SUBDOMAIN}" + if [[ ${IPV4:-ERR} == ${A_SUBDOMAIN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then + if verify_challenge_path "${SUBDOMAIN}.${SQL_DOMAIN}" 4; then + log_f "Confirmed A record ${A_SUBDOMAIN}" + VALIDATED_CONFIG_DOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}") + else + log_f "Confirmed AAAA record ${A_SUBDOMAIN}, but HTTP validation failed" + fi + else + log_f "Cannot match your IP ${IPV4} against hostname ${SUBDOMAIN}.${SQL_DOMAIN} (${A_SUBDOMAIN})" + fi + else + log_f "No A or AAAA record found for hostname ${SUBDOMAIN}.${SQL_DOMAIN}" + fi fi - elif [[ ! -z ${A_CONFIG} ]]; then - log_f "Found A record for autoconfig.${SQL_DOMAIN}: ${A_CONFIG}" - if [[ ${IPV4:-ERR} == ${A_CONFIG} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed A record autoconfig.${SQL_DOMAIN}" - VALIDATED_CONFIG_DOMAINS+=("autoconfig.${SQL_DOMAIN}") - else - log_f "Cannot match your IP ${IPV4} against hostname autoconfig.${SQL_DOMAIN} (${A_CONFIG})" - fi - else - log_f "No A or AAAA record found for hostname autoconfig.${SQL_DOMAIN}" - fi - - A_DISCOVER=$(dig A autodiscover.${SQL_DOMAIN} +short | tail -n 1) - AAAA_DISCOVER=$(dig AAAA autodiscover.${SQL_DOMAIN} +short | tail -n 1) - # Check if CNAME without v6 enabled target - if [[ ! -z ${AAAA_DISCOVER} ]] && [[ -z $(echo ${AAAA_DISCOVER} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then - AAAA_DISCOVER= - fi - if [[ ! -z ${AAAA_DISCOVER} ]]; then - log_f "Found AAAA record for autodiscover.${SQL_DOMAIN}: ${AAAA_DISCOVER} - skipping A record check" - if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_DISCOVER}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed AAAA record autodiscover.${SQL_DOMAIN}" - VALIDATED_CONFIG_DOMAINS+=("autodiscover.${SQL_DOMAIN}") - else - log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname autodiscover.${SQL_DOMAIN} ($(expand ${AAAA_DISCOVER}))" - fi - elif [[ ! -z ${A_DISCOVER} ]]; then - log_f "Found A record for autodiscover.${SQL_DOMAIN}: ${A_DISCOVER}" - if [[ ${IPV4:-ERR} == ${A_DISCOVER} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed A record autodiscover.${SQL_DOMAIN}" - VALIDATED_CONFIG_DOMAINS+=("autodiscover.${SQL_DOMAIN}") - else - log_f "Cannot match your IP ${IPV4} against hostname autodiscover.${SQL_DOMAIN} (${A_DISCOVER})" - fi - else - log_f "No A or AAAA record found for hostname autodiscover.${SQL_DOMAIN}" - fi + done done A_MAILCOW_HOSTNAME=$(dig A ${MAILCOW_HOSTNAME} +short | tail -n 1) @@ -238,16 +315,24 @@ while true; do if [[ ! -z ${AAAA_MAILCOW_HOSTNAME} ]]; then log_f "Found AAAA record for ${MAILCOW_HOSTNAME}: ${AAAA_MAILCOW_HOSTNAME} - skipping A record check" if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_MAILCOW_HOSTNAME}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed AAAA record ${MAILCOW_HOSTNAME}" - VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + if verify_challenge_path "${MAILCOW_HOSTNAME}" 6; then + log_f "Confirmed AAAA record ${AAAA_MAILCOW_HOSTNAME}" + VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + else + log_f "Confirmed AAAA record ${A_MAILCOW_HOSTNAME}, but HTTP validation failed" + fi else log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${MAILCOW_HOSTNAME} ($(expand ${AAAA_MAILCOW_HOSTNAME}))" fi elif [[ ! -z ${A_MAILCOW_HOSTNAME} ]]; then log_f "Found A record for ${MAILCOW_HOSTNAME}: ${A_MAILCOW_HOSTNAME}" if [[ ${IPV4:-ERR} == ${A_MAILCOW_HOSTNAME} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed A record ${A_MAILCOW_HOSTNAME}" - VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + if verify_challenge_path "${MAILCOW_HOSTNAME}" 4; then + log_f "Confirmed A record ${A_MAILCOW_HOSTNAME}" + VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + else + log_f "Confirmed A record ${A_MAILCOW_HOSTNAME}, but HTTP validation failed" + fi else log_f "Cannot match your IP ${IPV4} against hostname ${MAILCOW_HOSTNAME} (${A_MAILCOW_HOSTNAME})" fi @@ -279,16 +364,24 @@ while true; do if [[ ! -z ${AAAA_SAN} ]]; then log_f "Found AAAA record for ${SAN}: ${AAAA_SAN} - skipping A record check" if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_SAN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed AAAA record ${SAN}" - ADDITIONAL_VALIDATED_SAN+=("${SAN}") + if verify_challenge_path "${SAN}" 6; then + log_f "Confirmed AAAA record ${AAAA_SAN}" + ADDITIONAL_VALIDATED_SAN+=("${SAN}") + else + log_f "Confirmed AAAA record ${AAAA_SAN}, but HTTP validation failed" + fi else log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${SAN} ($(expand ${AAAA_SAN}))" fi elif [[ ! -z ${A_SAN} ]]; then log_f "Found A record for ${SAN}: ${A_SAN}" if [[ ${IPV4:-ERR} == ${A_SAN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed A record ${A_SAN}" - ADDITIONAL_VALIDATED_SAN+=("${SAN}") + if verify_challenge_path "${SAN}" 4; then + log_f "Confirmed A record ${A_SAN}" + ADDITIONAL_VALIDATED_SAN+=("${SAN}") + else + log_f "Confirmed A record ${A_SAN}, but HTTP validation failed" + fi else log_f "Cannot match your IP ${IPV4} against hostname ${SAN} (${A_SAN})" fi @@ -306,113 +399,98 @@ while true; do exec $(readlink -f "$0") fi - array_diff ORPHANED_SAN SAN_ARRAY_NOW ALL_VALIDATED - if [[ ! -z ${ORPHANED_SAN[*]} ]] && [[ ${ISSUER} != *"mailcow"* ]]; then - DATE=$(date +%Y-%m-%d_%H_%M_%S) - log_f "Found orphaned SAN ${ORPHANED_SAN[*]} in certificate, moving old files to ${ACME_BASE}/acme/private/${DATE}.bak/, keeping key file..." - mkdir -p ${ACME_BASE}/acme/private/${DATE}.bak/ - [[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/private/${DATE}.bak/ - [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && mv ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/acme/private/${DATE}.bak/ - [[ -f ${ACME_BASE}/acme/cert.pem ]] && mv ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/private/${DATE}.bak/ - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/private/${DATE}.bak/ # Keep key for TLSA 3 1 1 records + # Collecting SANs from active certificate + SAN_NAMES=$(openssl x509 -noout -text -in ${ACME_BASE}/cert.pem | awk '/X509v3 Subject Alternative Name/ {getline;gsub(/ /, "", $0); print}' | tr -d "DNS:") + if [[ ! -z ${SAN_NAMES} ]]; then + IFS=',' read -a SAN_ARRAY_NOW <<< ${SAN_NAMES} fi - ACME_RESPONSE=$(acme-client \ - -v -e -b -N -n \ - -a 'https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf' \ - -f ${ACME_BASE}/acme/private/account.key \ - -k ${ACME_BASE}/acme/private/privkey.pem \ - -c ${ACME_BASE}/acme \ - ${ALL_VALIDATED[*]} 2>&1 | tee /dev/fd/5) + # Finding difference in SAN array now vs. SAN array by current configuration + array_diff ORPHANED_SAN SAN_ARRAY_NOW ALL_VALIDATED + if [[ ! -z ${ORPHANED_SAN[*]} ]]; then + log_f "Found orphaned SANs ${ORPHANED_SAN[*]}" + SAN_CHANGE=1 + fi + array_diff ADDED_SAN ALL_VALIDATED SAN_ARRAY_NOW + if [[ ! -z ${ADDED_SAN[*]} ]]; then + log_f "Found new SANs ${ADDED_SAN[*]}" + SAN_CHANGE=1 + fi + + if [[ ${SAN_CHANGE} == 0 ]]; then + # Certificate did not change but could be due for renewal (4 weeks) + if ! openssl x509 -checkend 1209600 -noout -in ${ACME_BASE}/cert.pem; then + log_f "Certificate is due for renewal (< 2 weeks)" + else + log_f "Certificate validation done, neither changed nor due for renewal, sleeping for another day." + sleep 1d + continue + fi + fi + + DATE=$(date +%Y-%m-%d_%H_%M_%S) + log_f "Creating backups in ${ACME_BASE}/backups/${DATE}/ ..." + mkdir -p ${ACME_BASE}/backups/${DATE}/ + [[ -f ${ACME_BASE}/acme/acme.csr ]] && cp ${ACME_BASE}/acme/acme.csr ${ACME_BASE}/backups/${DATE}/ + [[ -f ${ACME_BASE}/acme/cert.pem ]] && cp ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/backups/${DATE}/ + [[ -f ${ACME_BASE}/acme/key.pem ]] && cp ${ACME_BASE}/acme/key.pem ${ACME_BASE}/backups/${DATE}/ + [[ -f ${ACME_BASE}/acme/account.pem ]] && cp ${ACME_BASE}/acme/account.pem ${ACME_BASE}/backups/${DATE}/ + + # Generating CSR + printf "[SAN]\nsubjectAltName=" > /tmp/_SAN + printf "DNS:%s," "${ALL_VALIDATED[@]}" >> /tmp/_SAN + sed -i '$s/,$//' /tmp/_SAN + openssl req -new -sha256 -key ${ACME_BASE}/acme/key.pem -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf /tmp/_SAN) > ${ACME_BASE}/acme/acme.csr + + if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + log_f "Using Let's Encrypt staging servers" + STAGING_PARAMETER='--directory-url https://acme-staging-v02.api.letsencrypt.org/directory' + else + STAGING_PARAMETER= + fi + + # acme-tiny writes info to stderr and ceritifcate to stdout + # The redirects will do the following: + # - redirect stdout to temp certificate file + # - redirect acme-tiny stderr to stdout (logs to variable ACME_RESPONSE) + # - tee stderr to get live output and log to dockerd + + ACME_RESPONSE=$(acme-tiny ${STAGING_PARAMETER} \ + --account-key ${ACME_BASE}/acme/account.pem \ + --disable-check \ + --csr ${ACME_BASE}/acme/acme.csr \ + --acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5) case "$?" in - 0) # new certs - log_f "${ACME_RESPONSE}" redis_only - # cp the new certificates and keys - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - - # restart docker containers - if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then - log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, restoring mailcow snake-oil and restarting containers..." - cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem - cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem - fi - restart_containers ${CONTAINERS_RESTART[*]} - ;; - 1) # failure - log_f "${ACME_RESPONSE}" redis_only - if [[ $ACME_RESPONSE =~ "No registration exists" ]]; then - log_f "Registration keys are invalid, deleting old keys and restarting..." - rm ${ACME_BASE}/acme/private/account.key + 0) # cert requested + ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) + log_f "${ACME_RESPONSE_B64}" redis_only b64 + log_f "Deploying..." + # Deploy the new certificate and key + # Moving temp cert to acme/cert.pem + if verify_hash_match /tmp/_cert.pem ${ACME_BASE}/acme/key.pem; then + mv /tmp/_cert.pem ${ACME_BASE}/acme/cert.pem + cp ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/cert.pem + cp ${ACME_BASE}/acme/key.pem ${ACME_BASE}/key.pem + reload_configurations + rm /var/www/acme/* + log_f "Certificate successfully deployed, removing backup, sleeping 1d" + sleep 1d + else + log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, ignoring certificate" + log_f "Retrying in 30 minutes..." + sleep 30m exec $(readlink -f "$0") fi - if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then - log_f "Error requesting certificate, restoring previous certificate from backup and restarting containers...." - cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - elif [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then - log_f "Error requesting certificate, restoring from previous acme request and restarting containers..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then - log_f "Error verifying certificates, restoring mailcow snake-oil and restarting containers..." - cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem - cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - [[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]} - log_f "Retrying in 30 minutes..." - sleep 30m - exec $(readlink -f "$0") ;; - 2) # no change - log_f "${ACME_RESPONSE}" redis_only - if ! diff ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem; then - log_f "Certificate was not changed, but active certificate does not match the verified certificate, fixing and restarting containers..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then - log_f "Certificate was not changed, but hashes do not match, restoring from previous acme request and restarting containers..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - log_f "Certificate was not changed" - [[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]} - ;; - *) # unspecified - log_f "${ACME_RESPONSE}" redis_only - if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then - log_f "Error requesting certificate, restoring previous certificate from backup and restarting containers...." - cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - elif [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then - log_f "Error requesting certificate, restoring from previous acme request and restarting containers..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then - log_f "Error verifying certificates, restoring mailcow snake-oil..." - cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem - cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - [[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]} + *) # non-zero is non-fun + ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) + log_f "${ACME_RESPONSE_B64}" redis_only b64 log_f "Retrying in 30 minutes..." + redis-cli -h redis SET ACME_FAIL_TIME "$(date +%s)" sleep 30m exec $(readlink -f "$0") ;; esac - log_f "ACME certificate validation done. Sleeping for another day." - sleep 1d - done diff --git a/data/Dockerfiles/clamd/Dockerfile b/data/Dockerfiles/clamd/Dockerfile index fc6f0a1f..f5a3903b 100644 --- a/data/Dockerfiles/clamd/Dockerfile +++ b/data/Dockerfiles/clamd/Dockerfile @@ -1,24 +1,37 @@ -FROM alpine:3.8 +FROM debian:stretch-slim LABEL maintainer "Andrรฉ Peters <andre.peters@servercow.de>" -# Add scripts -COPY dl_files.sh bootstrap.sh ./ - # Installation -ENV CLAMAV 0.100.1 +ENV CLAMAV 0.101.1 -RUN apk add --no-cache --virtual build-dependencies alpine-sdk ncurses-dev zlib-dev bzip2-dev pcre-dev linux-headers fts-dev libxml2-dev libressl-dev \ - && apk add --no-cache curl bash tini libxml2 libbz2 pcre fts libressl tzdata \ +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + zlib1g-dev \ + libncurses5-dev \ + libzip-dev \ + libpcre2-dev \ + libxml2-dev \ + libssl-dev \ + build-essential \ + libjson-c-dev \ + curl \ + bash \ + wget \ + tzdata \ + dnsutils \ + rsync \ + dos2unix \ + netcat \ + && rm -rf /var/lib/apt/lists/* \ && wget -O - https://www.clamav.net/downloads/production/clamav-${CLAMAV}.tar.gz | tar xfvz - \ && cd clamav-${CLAMAV} \ - && LIBS=-lfts ./configure \ + && ./configure \ --prefix=/usr \ --libdir=/usr/lib \ --sysconfdir=/etc/clamav \ --mandir=/usr/share/man \ --infodir=/usr/share/info \ - --without-iconv \ --disable-llvm \ --with-user=clamav \ --with-group=clamav \ @@ -30,18 +43,19 @@ RUN apk add --no-cache --virtual build-dependencies alpine-sdk ncurses-dev zlib- && make install \ && make clean \ && cd .. && rm -rf clamav-${CLAMAV} \ - && apk del build-dependencies \ - && addgroup -S clamav \ - && adduser -S -D -h /var/lib/clamav -s /sbin/nologin -G clamav -g clamav clamav \ - && adduser clamav tty \ - && mkdir -p /run/clamav \ - && chown clamav:clamav /run/clamav \ - && chmod +x /dl_files.sh \ - && set -ex; /bin/bash /dl_files.sh \ - && chmod 750 /run/clamav + && apt-get -y --auto-remove purge build-essential \ + && apt-get -y purge zlib1g-dev \ + libncurses5-dev \ + libzip-dev \ + libpcre2-dev \ + libxml2-dev \ + libssl-dev \ + libjson-c-dev \ + && addgroup --system --gid 700 clamav \ + && adduser --system --no-create-home --home /var/lib/clamav --uid 700 --gid 700 --disabled-login clamav \ + && rm -rf /tmp/* /var/tmp/* -# Port provision -EXPOSE 3310 +COPY bootstrap.sh ./ +COPY tini /sbin/tini -# AV daemon bootstrapping CMD ["/sbin/tini", "-g", "--", "/bootstrap.sh"] diff --git a/data/Dockerfiles/clamd/bootstrap.sh b/data/Dockerfiles/clamd/bootstrap.sh index ba1cee85..1d49cd20 100755 --- a/data/Dockerfiles/clamd/bootstrap.sh +++ b/data/Dockerfiles/clamd/bootstrap.sh @@ -6,16 +6,30 @@ if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then exit 0 fi -# Create log pipes -mkdir -p /var/log/clamav -touch /var/log/clamav/clamd.log /var/log/clamav/freshclam.log -chown -R clamav:clamav /var/log/clamav/ -chown root:tty /dev/console -chmod g+rw /dev/console +# Prepare whitelist + +mkdir -p /run/clamav /var/lib/clamav + +if [[ -s /etc/clamav/whitelist.ign2 ]]; then + echo "Copying non-empty whitelist.ign2 to /var/lib/clamav/whitelist.ign2" + cp /etc/clamav/whitelist.ign2 /var/lib/clamav/whitelist.ign2 +fi +if [[ ! -f /var/lib/clamav/whitelist.ign2 ]]; then + echo "Creating /var/lib/clamav/whitelist.ign2" + echo "Example-Signature.Ignore-1" > /var/lib/clamav/whitelist.ign2 +fi + +chown clamav:clamav -R /var/lib/clamav /run/clamav + +chmod 755 /var/lib/clamav +chmod 644 -R /var/lib/clamav/* +chmod 750 /run/clamav + +echo "Stating whitelist.ign2" +stat /var/lib/clamav/whitelist.ign2 -# Prepare -[[ ! -f /var/lib/clamav/whitelist.ign2 ]] && touch /var/lib/clamav/whitelist.ign2 dos2unix /var/lib/clamav/whitelist.ign2 + sed -i '/^\s*$/d' /var/lib/clamav/whitelist.ign2 BACKGROUND_TASKS=() @@ -29,7 +43,35 @@ done ) & BACKGROUND_TASKS+=($!) -clamd & +( +while true; do + sleep 2m + SANE_MIRRORS="$(dig +ignore +short rsync.sanesecurity.net)" + for sane_mirror in ${SANE_MIRRORS}; do + rsync -avp --chown=clamav:clamav --chmod=Du=rwx,Dgo=rx,Fu=rw,Fog=r --timeout=5 rsync://${sane_mirror}/sanesecurity/ \ + --include 'blurl.ndb' \ + --include 'junk.ndb' \ + --include 'jurlbl.ndb' \ + --include 'jurbla.ndb' \ + --include 'phishtank.ndb' \ + --include 'phish.ndb' \ + --include 'spamimg.hdb' \ + --include 'scam.ndb' \ + --include 'rogue.hdb' \ + --include 'sanesecurity.ftm' \ + --include 'sigwhitelist.ign2' \ + --exclude='*' /var/lib/clamav/ + if [ $? -eq 0 ]; then + echo RELOAD | nc localhost 3310 + break + fi + done + sleep 30h +done +) & +BACKGROUND_TASKS+=($!) + +nice -n10 clamd & BACKGROUND_TASKS+=($!) while true; do diff --git a/data/Dockerfiles/clamd/dl_files.sh b/data/Dockerfiles/clamd/dl_files.sh deleted file mode 100755 index 09d61241..00000000 --- a/data/Dockerfiles/clamd/dl_files.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -declare -a DB_MIRRORS=( - "switch.clamav.net" - "clamavdb.heanet.ie" - "clamav.iol.cz" - "clamav.univ-nantes.fr" - "clamav.easynet.fr" - "clamav.begi.net" -) -declare -a DB_MIRRORS=( $(shuf -e "${DB_MIRRORS[@]}") ) - -DB_FILES=( - "bytecode.cvd" - "daily.cvd" - "main.cvd" -) - -for i in "${DB_MIRRORS[@]}"; do - for j in "${DB_FILES[@]}"; do - [[ -f "/var/lib/clamav/${j}" && -s "/var/lib/clamav/${j}" ]] && continue; - if [[ $(curl -o /dev/null --connect-timeout 1 \ - --max-time 1 \ - --silent \ - --head \ - --write-out "%{http_code}\n" "${i}/${j}") == 200 ]]; then - curl "${i}/${j}" -o "/var/lib/clamav/${j}" -# - fi - done -done - -chown clamav:clamav /var/lib/clamav/*.cvd diff --git a/data/Dockerfiles/clamd/tini b/data/Dockerfiles/clamd/tini new file mode 100755 index 00000000..03af82f0 Binary files /dev/null and b/data/Dockerfiles/clamd/tini differ diff --git a/data/Dockerfiles/dockerapi/Dockerfile b/data/Dockerfiles/dockerapi/Dockerfile index 78389c4f..67bb6b07 100644 --- a/data/Dockerfiles/dockerapi/Dockerfile +++ b/data/Dockerfiles/dockerapi/Dockerfile @@ -1,8 +1,11 @@ -FROM python:2-alpine +FROM alpine:3.9 LABEL maintainer "Andre Peters <andre.peters@servercow.de>" -RUN apk add -U --no-cache iptables ip6tables tzdata -RUN pip install docker==3.0.1 flask flask-restful +RUN apk add -U --no-cache python2 python-dev py-pip gcc musl-dev tzdata openssl-dev libffi-dev \ + && pip2 install --upgrade pip \ + && pip2 install --upgrade docker==3.0.1 flask flask-restful pyOpenSSL \ + && apk del python-dev py2-pip gcc COPY server.py / + CMD ["python2", "-u", "/server.py"] diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py index e66cf238..d38775db 100644 --- a/data/Dockerfiles/dockerapi/server.py +++ b/data/Dockerfiles/dockerapi/server.py @@ -1,14 +1,19 @@ from flask import Flask from flask_restful import Resource, Api from flask import jsonify +from flask import Response from flask import request from threading import Thread +from OpenSSL import crypto import docker +import uuid import signal import time import os import re import sys +import ssl +import socket docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') app = Flask(__name__) @@ -62,65 +67,228 @@ class container_post(Resource): except Exception as e: return jsonify(type='danger', msg=str(e)) + elif post_action == 'top': + try: + for container in docker_client.containers.list(all=True, filters={"id": container_id}): + return jsonify(type='success', msg=container.top()) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + + elif post_action == 'stats': + try: + for container in docker_client.containers.list(all=True, filters={"id": container_id}): + return jsonify(type='success', msg=container.stats(decode=True, stream=False)) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + elif post_action == 'exec': if not request.json or not 'cmd' in request.json: return jsonify(type='danger', msg='cmd is missing') - if request.json['cmd'] == 'df' and request.json['dir']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - # Should be changed to be able to validate a path - directory = re.sub('[^0-9a-zA-Z/]+', '', request.json['dir']) - df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H " + directory + " | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') - if df_return.exit_code == 0: - return df_return.output.rstrip() - else: - return "0,0,0,0,0,0" - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'sieve_list' and request.json['username']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail') - return sieve_return.output - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"], user='vmail') - return sieve_return.output - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'worker_password' and request.json['raw']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - hash = container.exec_run(["/bin/bash", "-c", "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null"], user='_rspamd') - if hash.exit_code == 0: - hash_stdout = str(hash.output) - for line in hash_stdout.split("\n"): - if '$2$' in line: - hash = line.strip() - f = open("/access.inc", "w") - f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash.rstrip()) + '";\n') - f.close() - container.restart() - return jsonify(type='success', msg='command completed successfully') - else: - return jsonify(type='danger', msg='command did not complete, exit code was ' + int(hash.exit_code)) - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'mailman_password' and request.json['email'] and request.json['passwd']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - add_su = container.exec_run(["/bin/bash", "-c", "/opt/mm_web/add_su.py '" + request.json['passwd'].replace("'", "'\\''") + "' '" + request.json['email'].replace("'", "'\\''") + "'"], user='mailman') - if add_su.exit_code == 0: - return jsonify(type='success', msg='command completed successfully') - else: - return jsonify(type='danger', msg='command did not complete, exit code was ' + int(add_su.exit_code)) - except Exception as e: - return jsonify(type='danger', msg=str(e)) + if request.json['cmd'] == 'mailq': + if 'items' in request.json: + r = re.compile("^[0-9a-fA-F]+$") + filtered_qids = filter(r.match, request.json['items']) + if filtered_qids: + if request.json['task'] == 'delete': + flagged_qids = ['-d %s' % i for i in filtered_qids] + sanitized_string = str(' '.join(flagged_qids)); + try: + for container in docker_client.containers.list(filters={"id": container_id}): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return exec_run_handler('generic', postsuper_r) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + if request.json['task'] == 'hold': + flagged_qids = ['-h %s' % i for i in filtered_qids] + sanitized_string = str(' '.join(flagged_qids)); + try: + for container in docker_client.containers.list(filters={"id": container_id}): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return exec_run_handler('generic', postsuper_r) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + if request.json['task'] == 'unhold': + flagged_qids = ['-H %s' % i for i in filtered_qids] + sanitized_string = str(' '.join(flagged_qids)); + try: + for container in docker_client.containers.list(filters={"id": container_id}): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return exec_run_handler('generic', postsuper_r) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + if request.json['task'] == 'deliver': + flagged_qids = ['-i %s' % i for i in filtered_qids] + try: + for container in docker_client.containers.list(filters={"id": container_id}): + for i in flagged_qids: + postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') + # todo: check each exit code + return jsonify(type='success', msg=str("Scheduled immediate delivery")) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + elif request.json['task'] == 'list': + try: + for container in docker_client.containers.list(filters={"id": container_id}): + mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix') + return exec_run_handler('utf8_text_only', mailq_return) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + elif request.json['task'] == 'flush': + try: + for container in docker_client.containers.list(filters={"id": container_id}): + postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix') + return exec_run_handler('generic', postqueue_r) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + elif request.json['task'] == 'super_delete': + try: + for container in docker_client.containers.list(filters={"id": container_id}): + postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"]) + return exec_run_handler('generic', postsuper_r) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + + elif request.json['cmd'] == 'system': + if request.json['task'] == 'fts_rescan': + if 'username' in request.json: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm fts rescan -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail') + if rescan_return.exit_code == 0: + return jsonify(type='success', msg='fts_rescan: rescan triggered') + else: + return jsonify(type='warning', msg='fts_rescan error') + except Exception as e: + return jsonify(type='danger', msg=str(e)) + if 'all' in request.json: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm fts rescan -A"], user='vmail') + if rescan_return.exit_code == 0: + return jsonify(type='success', msg='fts_rescan: rescan triggered') + else: + return jsonify(type='warning', msg='fts_rescan error') + except Exception as e: + return jsonify(type='danger', msg=str(e)) + elif request.json['task'] == 'df': + if 'dir' in request.json: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request.json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') + if df_return.exit_code == 0: + return df_return.output.rstrip() + else: + return "0,0,0,0,0,0" + except Exception as e: + return jsonify(type='danger', msg=str(e)) + elif request.json['task'] == 'mysql_upgrade': + try: + for container in docker_client.containers.list(filters={"id": container_id}): + sql_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='mysql') + upgrade_cmd = "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n" + sql_socket = sql_shell.output; + try : + sql_socket.sendall(upgrade_cmd.encode('utf-8')) + sql_socket.shutdown(socket.SHUT_WR) + except socket.error: + return jsonify(type='danger', msg=str('socket error')) + worker_response = recv_socket_data(sql_socket) + matched = False + for line in worker_response.split("\n"): + if 'is already upgraded to' in line: + matched = True + if matched: + return jsonify(type='success', msg='mysql_upgrade: already upgraded') + else: + container.restart() + return jsonify(type='warning', msg='mysql_upgrade: upgrade was applied') + except Exception as e: + return jsonify(type='danger', msg=str(e)) + + elif request.json['cmd'] == 'reload': + if request.json['task'] == 'dovecot': + try: + for container in docker_client.containers.list(filters={"id": container_id}): + reload_return = container.exec_run(["/bin/bash", "-c", "/usr/local/sbin/dovecot reload"]) + return exec_run_handler('generic', reload_return) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + if request.json['task'] == 'postfix': + try: + for container in docker_client.containers.list(filters={"id": container_id}): + reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"]) + return exec_run_handler('generic', reload_return) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + if request.json['task'] == 'nginx': + try: + for container in docker_client.containers.list(filters={"id": container_id}): + reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"]) + return exec_run_handler('generic', reload_return) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + + elif request.json['cmd'] == 'sieve': + if request.json['task'] == 'list': + if 'username' in request.json: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"]) + return exec_run_handler('utf8_text_only', sieve_return) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + elif request.json['task'] == 'print': + if 'username' in request.json and 'script_name' in request.json: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"]) + return exec_run_handler('utf8_text_only', sieve_return) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + + elif request.json['cmd'] == 'maildir': + if request.json['task'] == 'cleanup': + if 'maildir' in request.json: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + sane_name = re.sub(r'\W+', '', request.json['maildir']) + maildir_cleanup = container.exec_run(["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"], user='vmail') + return exec_run_handler('generic', maildir_cleanup) + except Exception as e: + return jsonify(type='danger', msg=str(e)) + + elif request.json['cmd'] == 'rspamd': + if request.json['task'] == 'worker_password': + if 'raw' in request.json: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + worker_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='_rspamd') + worker_cmd = "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null\n" + worker_socket = worker_shell.output; + try : + worker_socket.sendall(worker_cmd.encode('utf-8')) + worker_socket.shutdown(socket.SHUT_WR) + except socket.error: + return jsonify(type='danger', msg=str('socket error')) + worker_response = recv_socket_data(worker_socket) + matched = False + for line in worker_response.split("\n"): + if '$2$' in line: + matched = True + hash = line.strip() + hash_out = re.search('\$2\$.+$', hash).group(0) + f = open("/access.inc", "w") + f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip()) + '";\n') + f.close() + container.restart() + if matched: + return jsonify(type='success', msg='command completed successfully') + else: + return jsonify(type='danger', msg='command did not complete') + except Exception as e: + return jsonify(type='danger', msg=str(e)) else: return jsonify(type='danger', msg='Unknown command') @@ -137,11 +305,84 @@ class GracefulKiller: signal.signal(signal.SIGINT, self.exit_gracefully) signal.signal(signal.SIGTERM, self.exit_gracefully) - def exit_gracefully(self,signum, frame): + def exit_gracefully(self, signum, frame): self.kill_now = True def startFlaskAPI(): - app.run(debug=False, host='0.0.0.0', port=8080, threaded=True) + create_self_signed_cert() + try: + ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ctx.check_hostname = False + ctx.load_cert_chain(certfile='/cert.pem', keyfile='/key.pem') + except: + print "Cannot initialize TLS, retrying in 5s..." + time.sleep(5) + app.run(debug=False, host='0.0.0.0', port=443, threaded=True, ssl_context=ctx) + +def recv_socket_data(c_socket, timeout=10): + c_socket.setblocking(0) + total_data=[]; + data=''; + begin=time.time() + while True: + if total_data and time.time()-begin > timeout: + break + elif time.time()-begin > timeout*2: + break + try: + data = c_socket.recv(8192) + if data: + total_data.append(data) + #change the beginning time for measurement + begin=time.time() + else: + #sleep for sometime to indicate a gap + time.sleep(0.1) + break + except: + pass + return ''.join(total_data) + +def exec_run_handler(type, output): + if type == 'generic': + if output.exit_code == 0: + return jsonify(type='success', msg='command completed successfully') + else: + return jsonify(type='danger', msg='command failed: ' + output.output) + if type == 'utf8_text_only': + r = Response(response=output.output, status=200, mimetype="text/plain") + r.headers["Content-Type"] = "text/plain; charset=utf-8" + return r + +def create_self_signed_cert(): + success = False + while not success: + try: + pkey = crypto.PKey() + pkey.generate_key(crypto.TYPE_RSA, 2048) + cert = crypto.X509() + cert.get_subject().O = "mailcow" + cert.get_subject().CN = "dockerapi" + cert.set_serial_number(int(uuid.uuid4())) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(10*365*24*60*60) + cert.set_issuer(cert.get_subject()) + cert.set_pubkey(pkey) + cert.sign(pkey, 'sha512') + cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert) + pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) + with os.fdopen(os.open('/cert.pem', os.O_WRONLY | os.O_CREAT, 0o644), 'w') as handle: + handle.write(cert) + with os.fdopen(os.open('/key.pem', os.O_WRONLY | os.O_CREAT, 0o600), 'w') as handle: + handle.write(pkey) + success = True + except: + time.sleep(1) + try: + os.remove('/cert.pem') + os.remove('/key.pem') + except OSError: + pass api.add_resource(containers_get, '/containers/json') api.add_resource(container_get, '/containers/<string:container_id>/json') diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 03323914..c5517499 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -3,8 +3,8 @@ LABEL maintainer "Andre Peters <andre.peters@servercow.de>" ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL C -ENV DOVECOT_VERSION 2.3.2.1 -ENV PIGEONHOLE_VERSION 0.5.2 +ENV DOVECOT_VERSION 2.3.5.1 +ENV PIGEONHOLE_VERSION 0.5.5 RUN apt-get update && apt-get -y --no-install-recommends install \ automake \ @@ -14,6 +14,9 @@ RUN apt-get update && apt-get -y --no-install-recommends install \ cpanminus \ curl \ default-libmysqlclient-dev \ + dnsutils \ + gettext \ + jq \ libjson-webtoken-perl \ libcgi-pm-perl \ libcrypt-openssl-rsa-perl \ @@ -38,6 +41,7 @@ RUN apt-get update && apt-get -y --no-install-recommends install \ libio-socket-ssl-perl \ libio-tee-perl \ libipc-run-perl \ + libldap2-dev \ liblockfile-simple-perl \ liblz-dev \ liblz4-dev \ @@ -60,6 +64,10 @@ RUN apt-get update && apt-get -y --no-install-recommends install \ libregexp-common-perl \ liburi-perl \ lzma-dev \ + python-html2text \ + python-jinja2 \ + python-mysql.connector \ + python-redis \ make \ mysql-client \ procps \ @@ -69,11 +77,10 @@ RUN apt-get update && apt-get -y --no-install-recommends install \ syslog-ng \ syslog-ng-core \ syslog-ng-mod-redis \ - && rm -rf /var/lib/apt/lists/* - -RUN curl https://www.dovecot.org/releases/2.3/dovecot-$DOVECOT_VERSION.tar.gz | tar xvz \ + && rm -rf /var/lib/apt/lists/* \ + && curl https://www.dovecot.org/releases/2.3/dovecot-$DOVECOT_VERSION.tar.gz | tar xvz \ && cd dovecot-$DOVECOT_VERSION \ - && ./configure --with-solr --with-mysql --with-lzma --with-lz4 --with-ssl=openssl --with-notify=inotify --with-storages=mdbox,sdbox,maildir,mbox,imapc,pop3c --with-bzlib --with-zlib \ + && ./configure --with-solr --with-mysql --with-ldap --with-lzma --with-lz4 --with-ssl=openssl --with-notify=inotify --with-storages=mdbox,sdbox,maildir,mbox,imapc,pop3c --with-bzlib --with-zlib --enable-hardening \ && make -j3 \ && make install \ && make clean \ @@ -85,12 +92,18 @@ RUN curl https://www.dovecot.org/releases/2.3/dovecot-$DOVECOT_VERSION.tar.gz | && make install \ && make clean \ && cd .. \ - && rm -rf dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION - -RUN cpanm Data::Uniqid Mail::IMAPClient String::Util -RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync -RUN echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync -RUN echo '* * * * * root /usr/local/bin/trim_logs.sh >> /dev/stdout 2>&1' > /etc/cron.d/trim_logs + && rm -rf dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION \ + && cpanm Data::Uniqid Mail::IMAPClient String::Util \ + && groupadd -g 5000 vmail \ + && groupadd -g 401 dovecot \ + && groupadd -g 402 dovenull \ + && useradd -g vmail -u 5000 vmail -d /var/vmail \ + && useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \ + && useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull \ + && touch /etc/default/locale \ + && apt-get purge -y build-essential automake autotools-dev default-libmysqlclient-dev libbz2-dev libcurl4-openssl-dev libexpat1-dev liblz-dev liblz4-dev liblzma-dev libpam-dev libssl-dev lzma-dev \ + && apt-get autoremove --purge -y \ + && rm -rf /tmp/* /var/tmp/* COPY trim_logs.sh /usr/local/bin/trim_logs.sh COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf @@ -101,31 +114,13 @@ COPY report-spam.sieve /usr/local/lib/dovecot/sieve/report-spam.sieve COPY report-ham.sieve /usr/local/lib/dovecot/sieve/report-ham.sieve COPY rspamd-pipe-ham /usr/local/lib/dovecot/sieve/rspamd-pipe-ham COPY rspamd-pipe-spam /usr/local/lib/dovecot/sieve/rspamd-pipe-spam +COPY sa-rules.sh /usr/local/bin/sa-rules.sh +COPY maildir_gc.sh /usr/local/bin/maildir_gc.sh COPY docker-entrypoint.sh / COPY supervisord.conf /etc/supervisor/supervisord.conf - -RUN chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \ - /usr/local/lib/dovecot/sieve/rspamd-pipe-spam \ - /usr/local/bin/imapsync_cron.pl \ - /usr/local/bin/postlogin.sh \ - /usr/local/bin/imapsync \ - /usr/local/bin/trim_logs.sh - -RUN groupadd -g 5000 vmail \ - && groupadd -g 401 dovecot \ - && groupadd -g 402 dovenull \ - && useradd -g vmail -u 5000 vmail -d /var/vmail \ - && useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \ - && useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull - -RUN touch /etc/default/locale -RUN apt-get purge -y build-essential automake autotools-dev default-libmysqlclient-dev libbz2-dev libcurl4-openssl-dev libexpat1-dev liblz-dev liblz4-dev liblzma-dev libpam-dev libssl-dev lzma-dev \ - && apt-get autoremove --purge -y +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh +COPY quarantine_notify.py /usr/local/bin/quarantine_notify.py +COPY quota_notify.py /usr/local/bin/quota_notify.py ENTRYPOINT ["/docker-entrypoint.sh"] CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf - -RUN rm -rf \ - /tmp/* \ - /var/tmp/* - diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 50e91a12..7034fc08 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -2,28 +2,35 @@ set -e # Wait for MySQL to warm-up -while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do +while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for database to come up..." sleep 2 done -# Hard-code env vars to scripts due to cron not passing them to the perl script -sed -i "/^\$DBUSER/c\\\$DBUSER='${DBUSER}';" /usr/local/bin/imapsync_cron.pl -sed -i "/^\$DBPASS/c\\\$DBPASS='${DBPASS}';" /usr/local/bin/imapsync_cron.pl -sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl -sed -i "s/LOG_LINES/${LOG_LINES}/g" /usr/local/bin/trim_logs.sh +# Hard-code env vars to scripts due to cron not passing them to the scripts +sed -i "s/__DBUSER__/${DBUSER}/g" /usr/local/bin/imapsync_cron.pl +sed -i "s/__DBPASS__/${DBPASS}/g" /usr/local/bin/imapsync_cron.pl +sed -i "s/__DBNAME__/${DBNAME}/g" /usr/local/bin/imapsync_cron.pl + +sed -i "s/__DBUSER__/${DBUSER}/g" /usr/local/bin/quarantine_notify.py +sed -i "s/__DBPASS__/${DBPASS}/g" /usr/local/bin/quarantine_notify.py +sed -i "s/__DBNAME__/${DBNAME}/g" /usr/local/bin/quarantine_notify.py + +sed -i "s/__LOG_LINES__/${LOG_LINES}/g" /usr/local/bin/trim_logs.sh # Create missing directories [[ ! -d /usr/local/etc/dovecot/sql/ ]] && mkdir -p /usr/local/etc/dovecot/sql/ +[[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage [[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve [[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo +[[ ! -d /var/volatile ]] && mkdir -p /var/volatile # Set Dovecot sql config parameters, escape " in db password DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g') # Create quota dict for Dovecot cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-quota.conf -connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/quota/storage table = quota2 @@ -40,7 +47,7 @@ EOF # Create dict used for sieve pre and postfilters cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf -connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/sieve/name/\$script_name table = sieve_before @@ -62,7 +69,7 @@ map { EOF cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf -connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/sieve/name/\$script_name table = sieve_after @@ -83,39 +90,72 @@ map { } EOF +echo -n ${ACL_ANYONE} > /usr/local/etc/dovecot/acl_anyone + +if [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then +echo -n 'quota acl zlib listescape mail_crypt mail_crypt_acl mail_log notify' > /usr/local/etc/dovecot/mail_plugins +echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape mail_crypt mail_crypt_acl notify mail_log' > /usr/local/etc/dovecot/mail_plugins_imap +echo -n 'quota sieve acl zlib listescape mail_crypt mail_crypt_acl' > /usr/local/etc/dovecot/mail_plugins_lmtp +else +echo -n 'quota acl zlib listescape mail_crypt mail_crypt_acl mail_log notify fts fts_solr' > /usr/local/etc/dovecot/mail_plugins +echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape mail_crypt mail_crypt_acl notify mail_log fts fts_solr' > /usr/local/etc/dovecot/mail_plugins_imap +echo -n 'quota sieve acl zlib listescape mail_crypt mail_crypt_acl fts fts_solr' > /usr/local/etc/dovecot/mail_plugins_lmtp +fi +chmod 644 /usr/local/etc/dovecot/mail_plugins /usr/local/etc/dovecot/mail_plugins_imap /usr/local/etc/dovecot/mail_plugins_lmtp /templates/quarantine.tpl -# Create userdb dict for Dovecot cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf driver = mysql -connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" -user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1' +connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/${MAILDIR_SUB}:VOLATILEDIR=/var/volatile/%u') AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1' iterate_query = SELECT username FROM mailbox WHERE active='1'; EOF # Create pass dict for Dovecot cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf driver = mysql -connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" default_pass_scheme = SSHA256 -password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, '$.force_pw_update') NOT LIKE '%%1%%' +password_query = SELECT password FROM mailbox WHERE active = '1' AND username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, '$.force_pw_update') NOT LIKE '%%1%%' EOF # Create global sieve_after script cat /usr/local/etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve -# Check permissions of vmail directory. +# Check permissions of vmail/attachments directory. # Do not do this every start-up, it may take a very long time. So we use a stat check here. if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi +if [[ $(stat -c %U /var/vmail/_garbage) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail/_garbage ; fi +if [[ $(stat -c %U /var/attachments) != "vmail" ]] ; then chown -R vmail:vmail /var/attachments ; fi + +# Cleanup random user maildirs +rm -rf /var/vmail/mailcow.local/* + # Create random master for SOGo sieve features RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1) -echo ${RAND_USER}@mailcow.local:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /usr/local/etc/dovecot/dovecot-master.passwd +echo ${RAND_USER}@mailcow.local:{SHA1}$(echo -n ${RAND_PASS} | sha1sum | awk '{print $1}') > /usr/local/etc/dovecot/dovecot-master.passwd +echo ${RAND_USER}@mailcow.local::5000:5000:::: > /usr/local/etc/dovecot/dovecot-master.userdb echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds +if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + # Create random master Password for SOGo 'login as user' via proxy auth + RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1) + echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass + cat <<EOF > /usr/local/etc/dovecot/sogo-sso.conf +passdb { + driver = static + args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS} +} +EOF +else + rm -f /usr/local/etc/dovecot/sogo-sso.pass + rm -f /usr/local/etc/dovecot/sogo-sso.conf +fi + # 401 is user dovecot -if [[ ! -f /mail_crypt/ecprivkey.pem || ! -f /mail_crypt/ecpubkey.pem ]]; then +if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem openssl pkey -in /mail_crypt/ecprivkey.pem -pubout -out /mail_crypt/ecpubkey.pem chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem @@ -129,7 +169,32 @@ sievec /usr/local/lib/dovecot/sieve/report-spam.sieve sievec /usr/local/lib/dovecot/sieve/report-ham.sieve # Fix permissions +chown root:root /usr/local/etc/dovecot/sql/*.conf +chown root:dovecot /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve* /usr/local/etc/dovecot/sql/dovecot-dict-sql-quota* +chmod 640 /usr/local/etc/dovecot/sql/*.conf chown -R vmail:vmail /var/vmail/sieve +chown -R vmail:vmail /var/volatile +adduser vmail tty +chmod g+rw /dev/console +chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \ + /usr/local/lib/dovecot/sieve/rspamd-pipe-spam \ + /usr/local/bin/imapsync_cron.pl \ + /usr/local/bin/postlogin.sh \ + /usr/local/bin/imapsync \ + /usr/local/bin/trim_logs.sh \ + /usr/local/bin/sa-rules.sh \ + /usr/local/bin/maildir_gc.sh \ + /usr/local/sbin/stop-supervisor.sh \ + /usr/local/bin/quota_notify.py + +# Setup cronjobs +echo '* * * * * root /usr/local/bin/imapsync_cron.pl 2>&1 | /usr/bin/logger' > /etc/cron.d/imapsync +echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync +echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/console 2>&1' > /etc/cron.d/trim_logs +echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/console 2>&1' > /etc/cron.d/maildir_gc +echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/console 2>&1' > /etc/cron.d/sa-rules +echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot-fts/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize +echo '*/20 * * * * vmail /usr/local/bin/quarantine_notify.py >> /dev/console 2>&1' > /etc/cron.d/quarantine_notify # Fix more than 1 hardlink issue touch /etc/crontab /etc/cron.*/* @@ -139,7 +204,13 @@ touch /etc/crontab /etc/cron.*/* # Clean stopped imapsync jobs rm -f /tmp/imapsync_busy.lock -IMAPSYNC_TABLE=$(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs) -[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'" +IMAPSYNC_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs) +[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'" + +# Envsubst maildir_gc +echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh + +# Collect SA rules once now +/usr/local/bin/sa-rules.sh exec "$@" diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index be5cb411..4fad97ab 100755 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -18,18 +18,20 @@ if ($imapsync_running eq 1) exit; } -sub qqw($) { split /\s+/, $_[0] } - -$DBNAME = ''; -$DBUSER = ''; -$DBPASS = ''; +sub qqw($) { + my @values = split('(?=--)', $_[0]); + foreach my $val (@values) { + $val=trim($val); + } + return @values +} $run_dir="/tmp"; -$dsn = "DBI:mysql:database=" . $DBNAME . ";host=mysql"; +$dsn = 'DBI:mysql:database=__DBNAME__;mysql_socket=/var/run/mysqld/mysqld.sock'; $lock_file = $run_dir . "/imapsync_busy"; $lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); $lockmgr->lock($lock_file) || die "can't lock ${lock_file}"; -$dbh = DBI->connect($dsn, $DBUSER, $DBPASS, { +$dbh = DBI->connect($dsn, '__DBUSER__', '__DBPASS__', { mysql_auto_reconnect => 1, mysql_enable_utf8mb4 => 1 }); diff --git a/data/Dockerfiles/dovecot/maildir_gc.sh b/data/Dockerfiles/dovecot/maildir_gc.sh new file mode 100755 index 00000000..24c1e461 --- /dev/null +++ b/data/Dockerfiles/dovecot/maildir_gc.sh @@ -0,0 +1,2 @@ +#/bin/bash +[ -d /var/vmail/_garbage/ ] && /usr/bin/find /var/vmail/_garbage/ -mindepth 1 -maxdepth 1 -type d -cmin +${MAILDIR_GC_TIME} -exec rm -r {} \; diff --git a/data/Dockerfiles/dovecot/postlogin.sh b/data/Dockerfiles/dovecot/postlogin.sh index 343910ff..01a45f31 100755 --- a/data/Dockerfiles/dovecot/postlogin.sh +++ b/data/Dockerfiles/dovecot/postlogin.sh @@ -1,4 +1,3 @@ #!/bin/sh - export MASTER_USER=$USER exec "$@" diff --git a/data/Dockerfiles/dovecot/quarantine_notify.py b/data/Dockerfiles/dovecot/quarantine_notify.py new file mode 100755 index 00000000..28a7aabe --- /dev/null +++ b/data/Dockerfiles/dovecot/quarantine_notify.py @@ -0,0 +1,125 @@ +#!/usr/bin/python + +import smtplib +import os +import mysql.connector +from email.MIMEMultipart import MIMEMultipart +from email.MIMEText import MIMEText +from email.Utils import COMMASPACE, formatdate +import cgi +import jinja2 +from jinja2 import Template +import json +import redis +import time +import html2text +import socket + +while True: + try: + r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0) + r.ping() + except Exception as ex: + print '%s - trying again...' % (ex) + time.sleep(3) + else: + break + +time_now = int(time.time()) + +def query_mysql(query, headers = True, update = False): + while True: + try: + cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user='__DBUSER__', passwd='__DBPASS__', database='__DBNAME__', charset="utf8") + except Exception as ex: + print '%s - trying again...' % (ex) + time.sleep(3) + else: + break + cur = cnx.cursor() + cur.execute(query) + if not update: + result = [] + columns = tuple( [d[0].decode('utf8') for d in cur.description] ) + for row in cur: + if headers: + result.append(dict(zip(columns, row))) + else: + result.append(row) + cur.close() + cnx.close() + return result + else: + cnx.commit() + cur.close() + cnx.close() + +def notify_rcpt(rcpt, msg_count, quarantine_acl): + meta_query = query_mysql('SELECT SHA2(CONCAT(id, qid), 256) AS qhash, id, subject, score, sender, created FROM quarantine WHERE notified = 0 AND rcpt = "%s"' % (rcpt)) + if r.get('Q_HTML'): + try: + template = Template(r.get('Q_HTML')) + except: + print "Error: Cannot parse quarantine template, falling back to default template." + with open('/templates/quarantine.tpl') as file_: + template = Template(file_.read()) + else: + with open('/templates/quarantine.tpl') as file_: + template = Template(file_.read()) + html = template.render(meta=meta_query, counter=msg_count, hostname=socket.gethostname(), quarantine_acl=quarantine_acl) + text = html2text.html2text(html) + count = 0 + while count < 15: + try: + server = smtplib.SMTP('postfix', 590, 'quarantine') + server.ehlo() + msg = MIMEMultipart('alternative') + msg['From'] = r.get('Q_SENDER') or "quarantine@localhost" + msg['Subject'] = r.get('Q_SUBJ') or "Spam Quarantine Notification" + msg['Date'] = formatdate(localtime = True) + text_part = MIMEText(text, 'plain', 'utf-8') + html_part = MIMEText(html, 'html', 'utf-8') + msg.attach(text_part) + msg.attach(html_part) + msg['To'] = str(rcpt) + text = msg.as_string() + server.sendmail(msg['From'], msg['To'], text) + server.quit() + for res in meta_query: + query_mysql('UPDATE quarantine SET notified = 1 WHERE id = "%d"' % (res['id']), update = True) + r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now) + break + except Exception as ex: + print '%s' % (ex) + time.sleep(3) + +records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt') + +for record in records: + attrs = '' + attrs_json = '' + try: + last_notification = int(r.hget('Q_LAST_NOTIFIED', record['rcpt'])) + if last_notification > time_now: + print 'Last notification is > time now, assuming never' + last_notification = 0 + except Exception as ex: + print 'Could not determine last notification for %s, assuming never' % (record['rcpt']) + last_notification = 0 + attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = "%s"' % (record['rcpt'])) + attrs = json.loads(str(attrs_json[0]['attributes'])) + if attrs['quarantine_notification'] not in ('hourly', 'daily', 'weekly', 'never'): + print 'Abnormal quarantine_notification value' + continue + if attrs['quarantine_notification'] == 'hourly': + if last_notification == 0 or (last_notification + 3600) < time_now: + print "Notifying %s about %d new items in quarantine" % (record['rcpt'], record['counter']) + notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl']) + elif attrs['quarantine_notification'] == 'daily': + if last_notification == 0 or (last_notification + 86400) < time_now: + print "Notifying %s about %d new items in quarantine" % (record['rcpt'], record['counter']) + notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl']) + elif attrs['quarantine_notification'] == 'weekly': + if last_notification == 0 or (last_notification + 604800) < time_now: + print "Notifying %s about %d new items in quarantine" % (record['rcpt'], record['counter']) + notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl']) diff --git a/data/Dockerfiles/dovecot/quota_notify.py b/data/Dockerfiles/dovecot/quota_notify.py new file mode 100755 index 00000000..f5df7639 --- /dev/null +++ b/data/Dockerfiles/dovecot/quota_notify.py @@ -0,0 +1,72 @@ +#!/usr/bin/python + +import smtplib +import os +from email.MIMEMultipart import MIMEMultipart +from email.MIMEText import MIMEText +from email.Utils import COMMASPACE, formatdate +import jinja2 +from jinja2 import Template +import redis +import time +import sys +import html2text +from subprocess import Popen, PIPE, STDOUT + +if len(sys.argv) > 2: + percent = int(sys.argv[1]) + username = str(sys.argv[2]) +else: + print "Args missing" + sys.exit(1) + +while True: + try: + r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0) + r.ping() + except Exception as ex: + print '%s - trying again...' % (ex) + time.sleep(3) + else: + break + +if r.get('QW_HTML'): + try: + template = Template(r.get('QW_HTML')) + except: + print "Error: Cannot parse quarantine template, falling back to default template." + with open('/templates/quota.tpl') as file_: + template = Template(file_.read()) +else: + with open('/templates/quota.tpl') as file_: + template = Template(file_.read()) + +html = template.render(username=username, percent=percent) +text = html2text.html2text(html) + +try: + msg = MIMEMultipart('alternative') + msg['From'] = r.get('QW_SENDER') or "quota-warning@localhost" + msg['Subject'] = r.get('QW_SUBJ') or "Quota warning" + msg['Date'] = formatdate(localtime = True) + text_part = MIMEText(text, 'plain', 'utf-8') + html_part = MIMEText(html, 'html', 'utf-8') + msg.attach(text_part) + msg.attach(html_part) + msg['To'] = username + p = Popen(['/usr/local/libexec/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT) + p.communicate(input=msg.as_string()) + +except Exception as ex: + print 'Failed to send quota notification: %s' % (ex) + sys.exit(1) + +try: + sys.stdout.close() +except: + pass + +try: + sys.stderr.close() +except: + pass diff --git a/data/Dockerfiles/dovecot/rspamd-pipe-ham b/data/Dockerfiles/dovecot/rspamd-pipe-ham index 9d961be0..9b26817c 100755 --- a/data/Dockerfiles/dovecot/rspamd-pipe-ham +++ b/data/Dockerfiles/dovecot/rspamd-pipe-ham @@ -3,7 +3,7 @@ FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 -cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/learnham -cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/fuzzyadd +cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnham +cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd exit 0 diff --git a/data/Dockerfiles/dovecot/rspamd-pipe-spam b/data/Dockerfiles/dovecot/rspamd-pipe-spam index 3b9e3497..d06aa919 100755 --- a/data/Dockerfiles/dovecot/rspamd-pipe-spam +++ b/data/Dockerfiles/dovecot/rspamd-pipe-spam @@ -3,7 +3,7 @@ FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 -cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/learnspam -cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/fuzzyadd +cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnspam +cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd exit 0 diff --git a/data/Dockerfiles/dovecot/sa-rules.sh b/data/Dockerfiles/dovecot/sa-rules.sh new file mode 100755 index 00000000..0cea240c --- /dev/null +++ b/data/Dockerfiles/dovecot/sa-rules.sh @@ -0,0 +1,25 @@ +#!/bin/bash +[[ ! -d /tmp/sa-rules-heinlein ]] && mkdir -p /tmp/sa-rules-heinlein +if [[ ! -f /etc/rspamd/custom/sa-rules-heinlein ]]; then + HASH_SA_RULES=0 +else + HASH_SA_RULES=$(cat /etc/rspamd/custom/sa-rules-heinlein | md5sum | cut -d' ' -f1) +fi + +curl --connect-timeout 15 --max-time 30 http://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"').tar.gz --output /tmp/sa-rules.tar.gz +if [[ -f /tmp/sa-rules.tar.gz ]]; then + tar xfvz /tmp/sa-rules.tar.gz -C /tmp/sa-rules-heinlein + # create complete list of rules in a single file + cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules-heinlein + # Only restart rspamd-mailcow when rules changed + if [[ $(cat /etc/rspamd/custom/sa-rules-heinlein | md5sum | cut -d' ' -f1) != ${HASH_SA_RULES} ]]; then + CONTAINER_NAME=rspamd-mailcow + CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | \ + jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | \ + jq -rc "select( .name | tostring | contains(\"${CONTAINER_NAME}\")) | .id") + if [[ ! -z ${CONTAINER_ID} ]]; then + curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi/containers/${CONTAINER_ID}/restart + fi + fi +fi +rm -rf /tmp/sa-rules-heinlein /tmp/sa-rules.tar.gz diff --git a/data/Dockerfiles/dovecot/stop-supervisor.sh b/data/Dockerfiles/dovecot/stop-supervisor.sh new file mode 100755 index 00000000..5394490c --- /dev/null +++ b/data/Dockerfiles/dovecot/stop-supervisor.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin diff --git a/data/Dockerfiles/dovecot/supervisord.conf b/data/Dockerfiles/dovecot/supervisord.conf index f517c388..2e3026a0 100644 --- a/data/Dockerfiles/dovecot/supervisord.conf +++ b/data/Dockerfiles/dovecot/supervisord.conf @@ -1,6 +1,7 @@ [supervisord] nodaemon=true user=root +pidfile=/var/run/supervisord.pid [program:syslog-ng] command=/usr/sbin/syslog-ng --foreground --no-caps @@ -17,3 +18,7 @@ autorestart=true [program:cron] command=/usr/sbin/cron -f autorestart=true + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/dovecot/trim_logs.sh b/data/Dockerfiles/dovecot/trim_logs.sh index b8da9740..2fec55d3 100755 --- a/data/Dockerfiles/dovecot/trim_logs.sh +++ b/data/Dockerfiles/dovecot/trim_logs.sh @@ -1,8 +1,18 @@ #!/bin/bash +catch_non_zero() { + CMD=${1} + ${CMD} > /dev/null + EC=$? + if [ ${EC} -ne 0 ]; then + echo "Command ${CMD} failed to execute, exit code was ${EC}" + fi +} +catch_non_zero "/usr/bin/redis-cli -h redis LTRIM ACME_LOG 0 __LOG_LINES__" +catch_non_zero "/usr/bin/redis-cli -h redis LTRIM POSTFIX_MAILLOG 0 __LOG_LINES__" +catch_non_zero "/usr/bin/redis-cli -h redis LTRIM DOVECOT_MAILLOG 0 __LOG_LINES__" +catch_non_zero "/usr/bin/redis-cli -h redis LTRIM SOGO_LOG 0 __LOG_LINES__" +catch_non_zero "/usr/bin/redis-cli -h redis LTRIM NETFILTER_LOG 0 __LOG_LINES__" +catch_non_zero "/usr/bin/redis-cli -h redis LTRIM AUTODISCOVER_LOG 0 __LOG_LINES__" +catch_non_zero "/usr/bin/redis-cli -h redis LTRIM API_LOG 0 __LOG_LINES__" +catch_non_zero "/usr/bin/redis-cli -h redis LTRIM RL_LOG 0 __LOG_LINES__" -redis-cli -h redis LTRIM ACME_LOG 0 LOG_LINES -redis-cli -h redis LTRIM POSTFIX_MAILLOG 0 LOG_LINES -redis-cli -h redis LTRIM DOVECOT_MAILLOG 0 LOG_LINES -redis-cli -h redis LTRIM SOGO_LOG 0 LOG_LINES -redis-cli -h redis LTRIM NETFILTER_LOG 0 LOG_LINES -redis-cli -h redis LTRIM AUTODISCOVER_LOG 0 LOG_LINES diff --git a/data/Dockerfiles/netfilter/Dockerfile b/data/Dockerfiles/netfilter/Dockerfile index aab9b1d3..92eaa39f 100644 --- a/data/Dockerfiles/netfilter/Dockerfile +++ b/data/Dockerfiles/netfilter/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.8 +FROM alpine:3.9 LABEL maintainer "Andre Peters <andre.peters@servercow.de>" ENV XTABLES_LIBDIR /usr/lib/xtables diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 9bf4a084..910679c6 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -10,7 +10,6 @@ from random import randint from threading import Thread from threading import Lock import redis -import time import json import iptc @@ -29,10 +28,11 @@ pubsub = r.pubsub() RULES = {} RULES[1] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed' RULES[2] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),' -RULES[3] = '-login: Aborted login \(no auth .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' -RULES[4] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' -RULES[5] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' -RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' +RULES[3] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' +RULES[4] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' +RULES[5] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' +RULES[6] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' +#RULES[7] = '-login: Aborted login \(no auth .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' bans = {} log = {} diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 3acfd09f..de31031a 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -1,11 +1,11 @@ -FROM php:7.2-fpm-alpine3.7 +FROM php:7.3-fpm-alpine3.8 LABEL maintainer "Andre Peters <andre.peters@servercow.de>" -ENV APCU_PECL 5.1.11 +ENV APCU_PECL 5.1.16 ENV IMAGICK_PECL 3.4.3 -ENV MAILPARSE_PECL 3.0.2 -ENV MEMCACHED_PECL 3.0.4 -ENV REDIS_PECL 4.0.2 +#ENV MAILPARSE_PECL 3.0.2 +ENV MEMCACHED_PECL 3.1.3 +ENV REDIS_PECL 4.2.0 RUN apk add -U --no-cache autoconf \ bash \ @@ -14,12 +14,14 @@ RUN apk add -U --no-cache autoconf \ freetype \ freetype-dev \ g++ \ + git \ gettext-dev \ icu-dev \ icu-libs \ imagemagick \ imagemagick-dev \ imap-dev \ + jq \ libjpeg-turbo \ libjpeg-turbo-dev \ libmemcached-dev \ @@ -32,6 +34,7 @@ RUN apk add -U --no-cache autoconf \ libwebp-dev \ libxml2-dev \ libxpm-dev \ + libzip-dev \ make \ mysql-client \ openldap-dev \ @@ -41,14 +44,13 @@ RUN apk add -U --no-cache autoconf \ samba-client \ zlib-dev \ tzdata \ - && pear install channel://pear.php.net/Net_IDNA2-0.2.0 \ - channel://pear.php.net/Auth_SASL-1.1.0 \ - Net_IMAP \ - Net_Sieve \ - NET_SMTP \ - Mail_mime \ - && pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} mailparse-${MAILPARSE_PECL} \ - && docker-php-ext-enable apcu imagick mailparse memcached redis \ + && git clone https://github.com/php/pecl-mail-mailparse \ + && cd pecl-mail-mailparse \ + && pecl install package.xml \ + && cd .. \ + && rm -r pecl-mail-mailparse \ + && pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \ + && docker-php-ext-enable apcu imagick memcached mailparse redis \ && pecl clear-cache \ && docker-php-ext-configure intl \ && docker-php-ext-configure gd \ diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh index 44d46f74..bf055f3a 100755 --- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh +++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh @@ -1,28 +1,67 @@ #!/bin/bash -set -e function array_by_comma { local IFS=","; echo "$*"; } # Wait for containers -while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do +while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do + echo "Waiting for SQL..." sleep 2 done until [[ $(redis-cli -h redis-mailcow PING) == "PONG" ]]; do + echo "Waiting for Redis..." sleep 2 done +# Set a default release format + +if [[ -z $(redis-cli --raw -h redis-mailcow GET Q_RELEASE_FORMAT) ]]; then + redis-cli --raw -h redis-mailcow SET Q_RELEASE_FORMAT raw +fi + +# Check of mysql_upgrade + +CONTAINER_ID= +# Todo: Better check if upgrade failed +# This can happen due to a broken sogo_view +[ -s /mysql_upgrade_loop ] && SQL_LOOP_C=$(cat /mysql_upgrade_loop) +until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do + CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | .id" 2> /dev/null) +done +echo "MySQL @ ${CONTAINER_ID}" +SQL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json' | jq -r .type) +if [[ ${SQL_UPGRADE_RETURN} == 'warning' ]]; then + if [ -z ${SQL_LOOP_C} ]; then + echo 1 > /mysql_upgrade_loop + echo "MySQL applied an upgrade, restarting PHP-FPM..." + exit 1 + else + rm /mysql_upgrade_loop + echo "MySQL was not applied previously, skipping. Restart php-fpm-mailcow to retry or run mysql_upgrade manually." + while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do + echo "Waiting for SQL to return..." + sleep 2 + done + fi +else + echo "MySQL is up-to-date" +fi + +# Trigger db init +echo "Running DB init..." +php -c /usr/local/etc/php -f /web/inc/init_db.inc.php + # Migrate domain map declare -a DOMAIN_ARR redis-cli -h redis-mailcow DEL DOMAIN_MAP while read line do DOMAIN_ARR+=("$line") -done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs) +done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs) while read line do DOMAIN_ARR+=("$line") -done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) +done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) if [[ ! -z ${DOMAIN_ARR} ]]; then for domain in "${DOMAIN_ARR[@]}"; do @@ -48,10 +87,9 @@ if [[ ${API_ALLOW_FROM} != "invalid" ]] && \ done VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]}) if [[ ! -z ${VALIDATED_IPS} ]]; then - mysql --host mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF -INSERT INTO api (username, api_key, active, allow_from) -SELECT username, "${API_KEY}", '1', "${VALIDATED_IPS}" FROM admin WHERE superadmin='1' AND active='1' -ON DUPLICATE KEY UPDATE active = '1', allow_from = "${VALIDATED_IPS}", api_key = "${API_KEY}"; + mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF +DELETE FROM api; +INSERT INTO api (api_key, active, allow_from) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}"); EOF fi fi diff --git a/data/Dockerfiles/postfix/Dockerfile b/data/Dockerfiles/postfix/Dockerfile index 9ad52fb2..05f2c3c7 100644 --- a/data/Dockerfiles/postfix/Dockerfile +++ b/data/Dockerfiles/postfix/Dockerfile @@ -48,6 +48,13 @@ COPY postfix.sh /opt/postfix.sh COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam COPY whitelist_forwardinghosts.sh /usr/local/bin/whitelist_forwardinghosts.sh +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh + +RUN chmod +x /opt/postfix.sh \ + /usr/local/bin/rspamd-pipe-ham \ + /usr/local/bin/rspamd-pipe-spam \ + /usr/local/bin/whitelist_forwardinghosts.sh \ + /usr/local/sbin/stop-supervisor.sh EXPOSE 588 diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 28e51ccd..d3e9ed0c 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -14,7 +14,7 @@ newaliases; cat <<EOF > /opt/postfix/conf/sql/mysql_relay_recipient_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT DISTINCT CASE WHEN '%d' IN ( @@ -29,10 +29,18 @@ query = SELECT DISTINCT END AS result; EOF +cat <<EOF > /opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf +user = ${DBUSER} +password = ${DBPASS} +hosts = unix:/var/run/mysqld/mysqld.sock +dbname = ${DBNAME} +query = SELECT CONCAT(policy, ' ', parameters) AS tls_policy FROM tls_policy_override WHERE active = '1' AND dest = '%s' +EOF + cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT IF(EXISTS( SELECT 'TLS_ACTIVE' FROM alias @@ -49,7 +57,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps FROM ( @@ -77,26 +85,49 @@ query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps AS transport_view; EOF -cat <<EOF > /opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf +cat <<EOF > /opt/postfix/conf/sql/mysql_transport_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock +dbname = ${DBNAME} +query = SELECT CONCAT('smtp_via_transport_maps:', nexthop) AS transport FROM transports + WHERE active = '1' + AND destination = '%s'; +EOF + +cat <<EOF > /opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf +user = ${DBUSER} +password = ${DBPASS} +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts WHERE id IN ( SELECT relayhost FROM domain WHERE CONCAT('@', domain) = '%s' - OR '%s' IN ( - SELECT CONCAT('@', alias_domain) FROM alias_domain + OR domain IN ( + SELECT target_domain FROM alias_domain WHERE CONCAT('@', alias_domain) = '%s' ) ) + AND active = '1' AND username != ''; EOF +cat <<EOF > /opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf +user = ${DBUSER} +password = ${DBPASS} +hosts = unix:/var/run/mysqld/mysqld.sock +dbname = ${DBNAME} +query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM transports + WHERE nexthop = '%s' + AND active = '1' + AND username != '' + LIMIT 1; +EOF + cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT goto FROM alias, alias_domain WHERE alias_domain.alias_domain = '%d' @@ -107,7 +138,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT username FROM mailbox, alias_domain WHERE alias_domain.alias_domain = '%d' @@ -119,7 +150,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT goto FROM alias WHERE address='%s' @@ -129,7 +160,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT bcc_dest FROM bcc_maps WHERE local_dest='%s' @@ -140,7 +171,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_sender_bcc_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT bcc_dest FROM bcc_maps WHERE local_dest='%s' @@ -151,7 +182,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT new_dest FROM recipient_maps WHERE old_dest='%s' @@ -161,7 +192,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_domains_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT alias_domain from alias_domain WHERE alias_domain='%s' AND active='1' UNION @@ -174,15 +205,15 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} -query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1' +query = SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%u/') FROM mailbox WHERE username='%s' AND active = '1' EOF cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '1' AND active = '1' EOF @@ -190,7 +221,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_sender_acl.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} # First select queries domain and alias_domain to determine if domains are active. query = SELECT goto FROM alias @@ -231,7 +262,7 @@ EOF cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf user = ${DBUSER} password = ${DBPASS} -hosts = mysql +hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT goto FROM spamalias WHERE address='%s' @@ -244,6 +275,8 @@ chmod 700 /var/lib/zeyple/keys chown -R 600:600 /var/lib/zeyple/keys # Fix Postfix permissions +chown -R root:postfix /opt/postfix/conf/sql/ +chmod 640 /opt/postfix/conf/sql/*.cf chgrp -R postdrop /var/spool/postfix/public chgrp -R postdrop /var/spool/postfix/maildrop postfix set-permissions diff --git a/data/Dockerfiles/postfix/rspamd-pipe-ham b/data/Dockerfiles/postfix/rspamd-pipe-ham index 9d961be0..9b26817c 100755 --- a/data/Dockerfiles/postfix/rspamd-pipe-ham +++ b/data/Dockerfiles/postfix/rspamd-pipe-ham @@ -3,7 +3,7 @@ FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 -cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/learnham -cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/fuzzyadd +cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnham +cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd exit 0 diff --git a/data/Dockerfiles/postfix/rspamd-pipe-spam b/data/Dockerfiles/postfix/rspamd-pipe-spam index 3b9e3497..d06aa919 100755 --- a/data/Dockerfiles/postfix/rspamd-pipe-spam +++ b/data/Dockerfiles/postfix/rspamd-pipe-spam @@ -3,7 +3,7 @@ FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 -cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/learnspam -cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/fuzzyadd +cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnspam +cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd exit 0 diff --git a/data/Dockerfiles/postfix/stop-supervisor.sh b/data/Dockerfiles/postfix/stop-supervisor.sh new file mode 100755 index 00000000..5394490c --- /dev/null +++ b/data/Dockerfiles/postfix/stop-supervisor.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin diff --git a/data/Dockerfiles/postfix/supervisord.conf b/data/Dockerfiles/postfix/supervisord.conf index b5ae6b54..27494bd6 100644 --- a/data/Dockerfiles/postfix/supervisord.conf +++ b/data/Dockerfiles/postfix/supervisord.conf @@ -13,3 +13,7 @@ autostart=true [program:postfix] command=/opt/postfix.sh autorestart=true + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index 67b8aa1c..87d92139 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:bionic LABEL maintainer "Andre Peters <andre.peters@servercow.de>" ARG DEBIAN_FRONTEND=noninteractive @@ -8,21 +8,21 @@ RUN apt-get update && apt-get install -y \ tzdata \ ca-certificates \ gnupg2 \ - gnupg-curl \ apt-transport-https \ && apt-key adv --fetch-keys https://rspamd.com/apt/gpg.key \ - && echo "deb https://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \ + && echo "deb https://rspamd.com/apt-stable/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ && apt-get update && apt-get install -y rspamd \ && rm -rf /var/lib/apt/lists/* \ - && echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local \ && apt-get autoremove --purge \ && apt-get clean \ && mkdir -p /run/rspamd \ && chown _rspamd:_rspamd /run/rspamd -COPY settings.conf /etc/rspamd/modules.d/settings.conf +COPY settings.conf /etc/rspamd/settings.conf COPY docker-entrypoint.sh /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] +STOPSIGNAL SIGTERM + CMD ["/usr/bin/rspamd", "-f", "-u", "_rspamd", "-g", "_rspamd"] diff --git a/data/Dockerfiles/rspamd/docker-entrypoint.sh b/data/Dockerfiles/rspamd/docker-entrypoint.sh index afb03bb6..6288550d 100755 --- a/data/Dockerfiles/rspamd/docker-entrypoint.sh +++ b/data/Dockerfiles/rspamd/docker-entrypoint.sh @@ -1,6 +1,9 @@ #!/bin/bash -chown -R _rspamd:_rspamd /var/lib/rspamd +chown -R _rspamd:_rspamd /var/lib/rspamd /etc/rspamd/local.d /etc/rspamd/override.d /etc/rspamd/custom +chmod 755 /var/lib/rspamd [[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Placeholder' > /etc/rspamd/override.d/worker-controller-password.inc +chown _rspamd:_rspamd /etc/rspamd/override.d/worker-controller-password.inc +[[ ! -f /etc/rspamd/custom/sa-rules-heinlein ]] && echo '# to be auto-filled by dovecot-mailcow' > /etc/rspamd/custom/sa-rules-heinlein exec "$@" diff --git a/data/Dockerfiles/rspamd/lua_util.lua b/data/Dockerfiles/rspamd/lua_util.lua deleted file mode 100644 index a9abd901..00000000 --- a/data/Dockerfiles/rspamd/lua_util.lua +++ /dev/null @@ -1,152 +0,0 @@ -local exports = {} -local lpeg = require 'lpeg' - -local split_grammar = {} -local function rspamd_str_split(s, sep) - local gr = split_grammar[sep] - - if not gr then - local _sep = lpeg.P(sep) - local elem = lpeg.C((1 - _sep)^0) - local p = lpeg.Ct(elem * (_sep * elem)^0) - gr = p - split_grammar[sep] = gr - end - - return gr:match(s) -end - -exports.rspamd_str_split = rspamd_str_split - -local space = lpeg.S' \t\n\v\f\r' -local nospace = 1 - space -local ptrim = space^0 * lpeg.C((space^0 * nospace^1)^0) -local match = lpeg.match -exports.rspamd_str_trim = function(s) - return match(ptrim, s) -end - --- Robert Jay Gould http://lua-users.org/wiki/SimpleRound -exports.round = function(num, numDecimalPlaces) - local mult = 10^(numDecimalPlaces or 0) - return math.floor(num * mult) / mult -end - -exports.template = function(tmpl, keys) - local var_lit = lpeg.P { lpeg.R("az") + lpeg.R("AZ") + lpeg.R("09") + "_" } - local var = lpeg.P { (lpeg.P("$") / "") * ((var_lit^1) / keys) } - local var_braced = lpeg.P { (lpeg.P("${") / "") * ((var_lit^1) / keys) * (lpeg.P("}") / "") } - - local template_grammar = lpeg.Cs((var + var_braced + 1)^0) - - return lpeg.match(template_grammar, tmpl) -end - -exports.remove_email_aliases = function(email_addr) - local function check_gmail_user(addr) - -- Remove all points - local no_dots_user = string.gsub(addr.user, '%.', '') - local cap, pluses = string.match(no_dots_user, '^([^%+][^%+]*)(%+.*)$') - if cap then - return cap, rspamd_str_split(pluses, '+'), nil - elseif no_dots_user ~= addr.user then - return no_dots_user,{},nil - end - - return nil - end - - local function check_address(addr) - if addr.user then - local cap, pluses = string.match(addr.user, '^([^%+][^%+]*)(%+.*)$') - if cap then - return cap, rspamd_str_split(pluses, '+'), nil - end - end - - return nil - end - - local function set_addr(addr, new_user, new_domain) - if new_user then - addr.user = new_user - end - if new_domain then - addr.domain = new_domain - end - - if addr.domain then - addr.addr = string.format('%s@%s', addr.user, addr.domain) - else - addr.addr = string.format('%s@', addr.user) - end - - if addr.name and #addr.name > 0 then - addr.raw = string.format('"%s" <%s>', addr.name, addr.addr) - else - addr.raw = string.format('<%s>', addr.addr) - end - end - - local function check_gmail(addr) - local nu, tags, nd = check_gmail_user(addr) - - if nu then - return nu, tags, nd - end - - return nil - end - - local function check_googlemail(addr) - local nd = 'gmail.com' - local nu, tags = check_gmail_user(addr) - - if nu then - return nu, tags, nd - end - - return nil, nil, nd - end - - local specific_domains = { - ['gmail.com'] = check_gmail, - ['googlemail.com'] = check_googlemail, - } - - if email_addr then - if email_addr.domain and specific_domains[email_addr.domain] then - local nu, tags, nd = specific_domains[email_addr.domain](email_addr) - if nu or nd then - set_addr(email_addr, nu, nd) - - return nu, tags - end - else - local nu, tags, nd = check_address(email_addr) - if nu or nd then - set_addr(email_addr, nu, nd) - - return nu, tags - end - end - - return nil - end -end - -exports.is_rspamc_or_controller = function(task) - local ua = task:get_request_header('User-Agent') or '' - local pwd = task:get_request_header('Password') - local is_rspamc = false - if tostring(ua) == 'rspamc' or pwd then is_rspamc = true end - - return is_rspamc -end - -local unpack_function = table.unpack or unpack -exports.unpack = function(t) - return unpack_function(t) -end - -return exports diff --git a/data/Dockerfiles/rspamd/metadata_exporter.lua b/data/Dockerfiles/rspamd/metadata_exporter.lua new file mode 100644 index 00000000..8420bd55 --- /dev/null +++ b/data/Dockerfiles/rspamd/metadata_exporter.lua @@ -0,0 +1,722 @@ +--[[ +Copyright (c) 2016, Andrew Lewis <nerf@judo.za.org> +Copyright (c) 2016, Vsevolod Stakhov <vsevolod@highsecure.ru> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +]]-- + +if confighelp then + return +end + +-- A plugin that pushes metadata (or whole messages) to external services + +local redis_params +local lua_util = require "lua_util" +local rspamd_http = require "rspamd_http" +local rspamd_tcp = require "rspamd_tcp" +local rspamd_util = require "rspamd_util" +local rspamd_logger = require "rspamd_logger" +local ucl = require "ucl" +local E = {} +local N = 'metadata_exporter' + +local settings = { + pusher_enabled = {}, + pusher_format = {}, + pusher_select = {}, + mime_type = 'text/plain', + defer = false, + mail_from = '', + mail_to = 'postmaster@localhost', + helo = 'rspamd', + email_template = [[From: "Rspamd" <$mail_from> +To: $mail_to +Subject: Spam alert +Date: $date +MIME-Version: 1.0 +Message-ID: <$our_message_id> +Content-type: text/plain; charset=utf-8 +Content-Transfer-Encoding: 8bit + +Authenticated username: $user +IP: $ip +Queue ID: $qid +SMTP FROM: $from +SMTP RCPT: $rcpt +MIME From: $header_from +MIME To: $header_to +MIME Date: $header_date +Subject: $header_subject +Message-ID: $message_id +Action: $action +Score: $score +Symbols: $symbols]], +} + +local function get_general_metadata(task, flatten, no_content) + local r = {} + local ip = task:get_from_ip() + if ip and ip:is_valid() then + r.ip = tostring(ip) + else + r.ip = 'unknown' + end + r.user = task:get_user() or 'unknown' + r.qid = task:get_queue_id() or 'unknown' + r.subject = task:get_subject() or 'unknown' + r.action = task:get_metric_action('default') + + local s = task:get_metric_score('default')[1] + r.score = flatten and string.format('%.2f', s) or s + + local rcpt = task:get_recipients('smtp') + if rcpt then + local l = {} + for _, a in ipairs(rcpt) do + table.insert(l, a['addr']) + end + if not flatten then + r.rcpt = l + else + r.rcpt = table.concat(l, ', ') + end + else + r.rcpt = 'unknown' + end + local from = task:get_from('smtp') + if ((from or E)[1] or E).addr then + r.from = from[1].addr + else + r.from = 'unknown' + end + local syminf = task:get_symbols_all() + if flatten then + local l = {} + for _, sym in ipairs(syminf) do + local txt + if sym.options then + local topt = table.concat(sym.options, ', ') + txt = sym.name .. '(' .. string.format('%.2f', sym.score) .. ')' .. ' [' .. topt .. ']' + else + txt = sym.name .. '(' .. string.format('%.2f', sym.score) .. ')' + end + table.insert(l, txt) + end + r.symbols = table.concat(l, '\n\t') + else + r.symbols = syminf + end + local function process_header(name) + local hdr = task:get_header_full(name) + if hdr then + local l = {} + for _, h in ipairs(hdr) do + table.insert(l, h.decoded) + end + if not flatten then + return l + else + return table.concat(l, '\n') + end + else + return 'unknown' + end + end + if not no_content then + r.header_from = process_header('from') + r.header_to = process_header('to') + r.header_subject = process_header('subject') + r.header_date = process_header('date') + r.message_id = task:get_message_id() + end + return r +end + +local formatters = { + default = function(task) + return task:get_content() + end, + email_alert = function(task, rule, extra) + local meta = get_general_metadata(task, true) + local display_emails = {} + meta.mail_from = rule.mail_from or settings.mail_from + local mail_targets = rule.mail_to or settings.mail_to + if type(mail_targets) ~= 'table' then + table.insert(display_emails, string.format('<%s>', mail_targets)) + mail_targets = {[mail_targets] = true} + else + for _, e in ipairs(mail_targets) do + table.insert(display_emails, string.format('<%s>', e)) + end + end + if rule.email_alert_sender then + local x = task:get_from('smtp') + if x and string.len(x[1].addr) > 0 then + mail_targets[x] = true + table.insert(display_emails, string.format('<%s>', x[1].addr)) + end + end + if rule.email_alert_user then + local x = task:get_user() + if x then + mail_targets[x] = true + table.insert(display_emails, string.format('<%s>', x)) + end + end + if rule.email_alert_recipients then + local x = task:get_recipients('smtp') + if x then + for _, e in ipairs(x) do + if string.len(e.addr) > 0 then + mail_targets[e.addr] = true + table.insert(display_emails, string.format('<%s>', e.addr)) + end + end + end + end + meta.mail_to = table.concat(display_emails, ', ') + meta.our_message_id = rspamd_util.random_hex(12) .. '@rspamd' + meta.date = rspamd_util.time_to_string(rspamd_util.get_time()) + return lua_util.template(rule.email_template or settings.email_template, meta), { mail_targets = mail_targets} + end, + json = function(task) + return ucl.to_format(get_general_metadata(task), 'json-compact') + end +} + +local function is_spam(action) + return (action == 'reject' or action == 'add header' or action == 'rewrite subject') +end + +local selectors = { + default = function(task) + return true + end, + is_spam = function(task) + local action = task:get_metric_action('default') + return is_spam(action) + end, + is_spam_authed = function(task) + if not task:get_user() then + return false + end + local action = task:get_metric_action('default') + return is_spam(action) + end, + is_reject = function(task) + local action = task:get_metric_action('default') + return (action == 'reject') + end, + is_reject_authed = function(task) + if not task:get_user() then + return false + end + local action = task:get_metric_action('default') + return (action == 'reject') + end, +} + +local function maybe_defer(task, rule) + if rule.defer then + rspamd_logger.warnx(task, 'deferring message') + task:set_pre_result('soft reject', 'deferred', N) + end +end + +local pushers = { + redis_pubsub = function(task, formatted, rule) + local _,ret,upstream + local function redis_pub_cb(err) + if err then + rspamd_logger.errx(task, 'got error %s when publishing on server %s', + err, upstream:get_addr()) + return maybe_defer(task, rule) + end + return true + end + ret,_,upstream = rspamd_redis_make_request(task, + redis_params, -- connect params + nil, -- hash key + true, -- is write + redis_pub_cb, --callback + 'PUBLISH', -- command + {rule.channel, formatted} -- arguments + ) + if not ret then + rspamd_logger.errx(task, 'error connecting to redis') + maybe_defer(task, rule) + end + end, + http = function(task, formatted, rule) + local function http_callback(err, code) + if err then + rspamd_logger.errx(task, 'got error %s in http callback', err) + return maybe_defer(task, rule) + end + if code ~= 200 then + rspamd_logger.errx(task, 'got unexpected http status: %s', code) + return maybe_defer(task, rule) + end + return true + end + local hdrs = {} + if rule.meta_headers then + local gm = get_general_metadata(task, false, true) + local pfx = rule.meta_header_prefix or 'X-Rspamd-' + for k, v in pairs(gm) do + if type(v) == 'table' then + hdrs[pfx .. k] = ucl.to_format(v, 'json-compact') + else + hdrs[pfx .. k] = v + end + end + end + rspamd_http.request({ + task=task, + url=rule.url, + body=formatted, + callback=http_callback, + mime_type=rule.mime_type or settings.mime_type, + headers=hdrs, + }) + end, + send_mail = function(task, formatted, rule, extra) + local function mail_cb(err, data, conn) + local function no_error(merr, mdata, wantcode) + wantcode = wantcode or '2' + if merr then + rspamd_logger.errx(task, 'got error in tcp callback: %s', merr) + if conn then + conn:close() + end + maybe_defer(task, rule) + return false + end + if mdata then + if type(mdata) ~= 'string' then + mdata = tostring(mdata) + end + if string.sub(mdata, 1, 1) ~= wantcode then + rspamd_logger.errx(task, 'got bad smtp response: %s', mdata) + if conn then + conn:close() + end + maybe_defer(task, rule) + return false + end + else + rspamd_logger.errx(task, 'no data') + if conn then + conn:close() + end + maybe_defer(task, rule) + return false + end + return true + end + local function all_done_cb(merr, mdata) + if conn then + conn:close() + end + return true + end + local function quit_done_cb(merr, mdata) + conn:add_read(all_done_cb, '\r\n') + end + local function quit_cb(merr, mdata) + if no_error(merr, mdata) then + conn:add_write(quit_done_cb, 'QUIT\r\n') + end + end + local function pre_quit_cb(merr, mdata) + if no_error(merr, '2') then + conn:add_read(quit_cb, '\r\n') + end + end + local function data_done_cb(merr, mdata) + if no_error(merr, mdata, '3') then + conn:add_write(pre_quit_cb, {formatted, '\r\n.\r\n'}) + end + end + local function data_cb(merr, mdata) + if no_error(merr, '2') then + conn:add_read(data_done_cb, '\r\n') + end + end + local from_done_cb + local function rcpt_done_cb(merr, mdata) + if no_error(merr, mdata) then + local k = next(extra.mail_targets) + if not k then + conn:add_write(data_cb, 'DATA\r\n') + else + from_done_cb('2', '2') + end + end + end + local function rcpt_cb(merr, mdata) + if no_error(merr, '2') then + conn:add_read(rcpt_done_cb, '\r\n') + end + end + from_done_cb = function(merr, mdata) + local k + if extra then + k = next(extra.mail_targets) + else + extra = {mail_targets = {}} + if type(rule.mail_to) == 'string' then + extra = {mail_targets = {}} + k = rule.mail_to + elseif type(rule.mail_to) == 'table' then + for _, r in ipairs(rule.mail_to) do + extra.mail_targets[r] = true + end + k = next(extra.mail_targets) + end + end + extra.mail_targets[k] = nil + conn:add_write(rcpt_cb, {'RCPT TO: <', k, '>\r\n'}) + end + local function from_cb(merr, mdata) + if no_error(merr, '2') then + conn:add_read(from_done_cb, '\r\n') + end + end + local function hello_done_cb(merr, mdata) + if no_error(merr, mdata) then + conn:add_write(from_cb, {'MAIL FROM: <', rule.mail_from or settings.mail_from, '>\r\n'}) + end + end + local function hello_cb(merr) + if no_error(merr, '2') then + conn:add_read(hello_done_cb, '\r\n') + end + end + if no_error(err, data) then + conn:add_write(hello_cb, {'HELO ', rule.helo or settings.helo, '\r\n'}) + end + end + rspamd_tcp.request({ + task = task, + callback = mail_cb, + stop_pattern = '\r\n', + host = rule.smtp, + port = rule.smtp_port or settings.smtp_port or 25, + }) + end, +} + +local opts = rspamd_config:get_all_opt(N) +if not opts then return end +local process_settings = { + select = function(val) + selectors.custom = assert(load(val))() + end, + format = function(val) + formatters.custom = assert(load(val))() + end, + push = function(val) + pushers.custom = assert(load(val))() + end, + custom_push = function(val) + if type(val) == 'table' then + for k, v in pairs(val) do + pushers[k] = assert(load(v))() + end + end + end, + custom_select = function(val) + if type(val) == 'table' then + for k, v in pairs(val) do + selectors[k] = assert(load(v))() + end + end + end, + custom_format = function(val) + if type(val) == 'table' then + for k, v in pairs(val) do + formatters[k] = assert(load(v))() + end + end + end, + pusher_enabled = function(val) + if type(val) == 'string' then + if pushers[val] then + settings.pusher_enabled[val] = true + else + rspamd_logger.errx(rspamd_config, 'Pusher type: %s is invalid', val) + end + elseif type(val) == 'table' then + for _, v in ipairs(val) do + if pushers[v] then + settings.pusher_enabled[v] = true + else + rspamd_logger.errx(rspamd_config, 'Pusher type: %s is invalid', val) + end + end + end + end, +} +for k, v in pairs(opts) do + local f = process_settings[k] + if f then + f(opts[k]) + else + settings[k] = v + end +end +if type(settings.rules) ~= 'table' then + -- Legacy config + settings.rules = {} + if not next(settings.pusher_enabled) then + if pushers.custom then + rspamd_logger.infox(rspamd_config, 'Custom pusher implicitly enabled') + settings.pusher_enabled.custom = true + else + -- Check legacy options + if settings.url then + rspamd_logger.warnx(rspamd_config, 'HTTP pusher implicitly enabled') + settings.pusher_enabled.http = true + end + if settings.channel then + rspamd_logger.warnx(rspamd_config, 'Redis Pubsub pusher implicitly enabled') + settings.pusher_enabled.redis_pubsub = true + end + if settings.smtp and settings.mail_to then + rspamd_logger.warnx(rspamd_config, 'SMTP pusher implicitly enabled') + settings.pusher_enabled.send_mail = true + end + end + end + if not next(settings.pusher_enabled) then + rspamd_logger.errx(rspamd_config, 'No push backend enabled') + return + end + if settings.formatter then + settings.format = formatters[settings.formatter] + if not settings.format then + rspamd_logger.errx(rspamd_config, 'No such formatter: %s', settings.formatter) + return + end + end + if settings.selector then + settings.select = selectors[settings.selector] + if not settings.select then + rspamd_logger.errx(rspamd_config, 'No such selector: %s', settings.selector) + return + end + end + for k in pairs(settings.pusher_enabled) do + local formatter = settings.pusher_format[k] + local selector = settings.pusher_select[k] + if not formatter then + settings.pusher_format[k] = settings.formatter or 'default' + rspamd_logger.infox(rspamd_config, 'Using default formatter for %s pusher', k) + else + if not formatters[formatter] then + rspamd_logger.errx(rspamd_config, 'No such formatter: %s - disabling %s', formatter, k) + settings.pusher_enabled.k = nil + end + end + if not selector then + settings.pusher_select[k] = settings.selector or 'default' + rspamd_logger.infox(rspamd_config, 'Using default selector for %s pusher', k) + else + if not selectors[selector] then + rspamd_logger.errx(rspamd_config, 'No such selector: %s - disabling %s', selector, k) + settings.pusher_enabled.k = nil + end + end + end + if settings.pusher_enabled.redis_pubsub then + redis_params = rspamd_parse_redis_server(N) + if not redis_params then + rspamd_logger.errx(rspamd_config, 'No redis servers are specified') + settings.pusher_enabled.redis_pubsub = nil + else + local r = {} + r.backend = 'redis_pubsub' + r.channel = settings.channel + r.defer = settings.defer + r.selector = settings.pusher_select.redis_pubsub + r.formatter = settings.pusher_format.redis_pubsub + settings.rules[r.backend:upper()] = r + end + end + if settings.pusher_enabled.http then + if not settings.url then + rspamd_logger.errx(rspamd_config, 'No URL is specified') + settings.pusher_enabled.http = nil + else + local r = {} + r.backend = 'http' + r.url = settings.url + r.mime_type = settings.mime_type + r.defer = settings.defer + r.selector = settings.pusher_select.http + r.formatter = settings.pusher_format.http + settings.rules[r.backend:upper()] = r + end + end + if settings.pusher_enabled.send_mail then + if not (settings.mail_to and settings.smtp) then + rspamd_logger.errx(rspamd_config, 'No mail_to and/or smtp setting is specified') + settings.pusher_enabled.send_mail = nil + else + local r = {} + r.backend = 'send_mail' + r.mail_to = settings.mail_to + r.mail_from = settings.mail_from + r.helo = settings.hello + r.smtp = settings.smtp + r.smtp_port = settings.smtp_port + r.email_template = settings.email_template + r.defer = settings.defer + r.selector = settings.pusher_select.send_mail + r.formatter = settings.pusher_format.send_mail + settings.rules[r.backend:upper()] = r + end + end + if not next(settings.pusher_enabled) then + rspamd_logger.errx(rspamd_config, 'No push backend enabled') + return + end +elseif not next(settings.rules) then + lua_util.debugm(N, rspamd_config, 'No rules enabled') + return +end +if not settings.rules or not next(settings.rules) then + rspamd_logger.errx(rspamd_config, 'No rules enabled') + return +end +local backend_required_elements = { + http = { + 'url', + }, + smtp = { + 'mail_to', + 'smtp', + }, + redis_pubsub = { + 'channel', + }, +} +local check_element = { + selector = function(k, v) + if not selectors[v] then + rspamd_logger.errx(rspamd_config, 'Rule %s has invalid selector %s', k, v) + return false + else + return true + end + end, + formatter = function(k, v) + if not formatters[v] then + rspamd_logger.errx(rspamd_config, 'Rule %s has invalid formatter %s', k, v) + return false + else + return true + end + end, +} +local backend_check = { + default = function(k, rule) + local reqset = backend_required_elements[rule.backend] + if reqset then + for _, e in ipairs(reqset) do + if not rule[e] then + rspamd_logger.errx(rspamd_config, 'Rule %s misses required setting %s', k, e) + settings.rules[k] = nil + end + end + end + for sett, v in pairs(rule) do + local f = check_element[sett] + if f then + if not f(sett, v) then + settings.rules[k] = nil + end + end + end + end, +} +backend_check.redis_pubsub = function(k, rule) + if not redis_params then + redis_params = rspamd_parse_redis_server(N) + end + if not redis_params then + rspamd_logger.errx(rspamd_config, 'No redis servers are specified') + settings.rules[k] = nil + else + backend_check.default(k, rule) + end +end +setmetatable(backend_check, { + __index = function() + return backend_check.default + end, +}) +for k, v in pairs(settings.rules) do + if type(v) == 'table' then + local backend = v.backend + if not backend then + rspamd_logger.errx(rspamd_config, 'Rule %s has no backend', k) + settings.rules[k] = nil + elseif not pushers[backend] then + rspamd_logger.errx(rspamd_config, 'Rule %s has invalid backend %s', k, backend) + settings.rules[k] = nil + else + local f = backend_check[backend] + f(k, v) + end + else + rspamd_logger.errx(rspamd_config, 'Rule %s has bad type: %s', k, type(v)) + settings.rules[k] = nil + end +end + +local function gen_exporter(rule) + return function (task) + if task:has_flag('skip') then return end + local selector = rule.selector or 'default' + local selected = selectors[selector](task) + if selected then + lua_util.debugm(N, task, 'Message selected for processing') + local formatter = rule.formatter or 'default' + local formatted, extra = formatters[formatter](task, rule) + if formatted then + pushers[rule.backend](task, formatted, rule, extra) + else + lua_util.debugm(N, task, 'Formatter [%s] returned non-truthy value [%s]', formatter, formatted) + end + else + lua_util.debugm(N, task, 'Selector [%s] returned non-truthy value [%s]', selector, selected) + end + end +end + +if not next(settings.rules) then + rspamd_logger.errx(rspamd_config, 'No rules enabled') + lua_util.disable_module(N, "config") +end +for k, r in pairs(settings.rules) do + rspamd_config:register_symbol({ + name = 'EXPORT_METADATA_' .. k, + type = 'postfilter,idempotent', + callback = gen_exporter(r), + priority = 10, + flags = 'empty', + }) +end diff --git a/data/Dockerfiles/rspamd/ratelimit.lua b/data/Dockerfiles/rspamd/ratelimit.lua deleted file mode 100644 index 839ec5c6..00000000 --- a/data/Dockerfiles/rspamd/ratelimit.lua +++ /dev/null @@ -1,674 +0,0 @@ ---[[ -Copyright (c) 2011-2017, Vsevolod Stakhov <vsevolod@highsecure.ru> -Copyright (c) 2016-2017, Andrew Lewis <nerf@judo.za.org> - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -]]-- - -if confighelp then - return -end - --- A plugin that implements ratelimits using redis - -local E = {} -local N = 'ratelimit' -local redis_params --- Senders that are considered as bounce -local settings = { - bounce_senders = { 'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-daemon', 'mdaemon' }, --- Do not check ratelimits for these recipients - whitelisted_rcpts = { 'postmaster', 'mailer-daemon' }, - prefix = 'RL', - ham_factor_rate = 1.01, - spam_factor_rate = 0.99, - ham_factor_burst = 1.02, - spam_factor_burst = 0.98, - max_rate_mult = 5, - max_bucket_mult = 10, - expire = 60 * 60 * 24 * 2, -- 2 days by default - limits = {}, - allow_local = false, -} - --- Checks bucket, updating it if needed --- KEYS[1] - prefix to update, e.g. RL_<triplet>_<seconds> --- KEYS[2] - current time in milliseconds --- KEYS[3] - bucket leak rate (messages per millisecond) --- KEYS[4] - bucket burst --- KEYS[5] - expire for a bucket --- return 1 if message should be ratelimited and 0 if not --- Redis keys used: --- l - last hit --- b - current burst --- dr - current dynamic rate multiplier (*10000) --- db - current dynamic burst multiplier (*10000) -local bucket_check_script = [[ - local last = redis.call('HGET', KEYS[1], 'l') - local now = tonumber(KEYS[2]) - local dynr, dynb = 0, 0 - if not last then - -- New bucket - redis.call('HSET', KEYS[1], 'l', KEYS[2]) - redis.call('HSET', KEYS[1], 'b', '0') - redis.call('HSET', KEYS[1], 'dr', '10000') - redis.call('HSET', KEYS[1], 'db', '10000') - redis.call('EXPIRE', KEYS[1], KEYS[5]) - return {0, 0, 1, 1} - end - - last = tonumber(last) - local burst = tonumber(redis.call('HGET', KEYS[1], 'b')) - -- Perform leak - if burst > 0 then - if last < tonumber(KEYS[2]) then - local rate = tonumber(KEYS[3]) - dynr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000.0 - rate = rate * dynr - local leaked = ((now - last) * rate) - burst = burst - leaked - redis.call('HINCRBYFLOAT', KEYS[1], 'b', -(leaked)) - end - else - burst = 0 - redis.call('HSET', KEYS[1], 'b', '0') - end - - dynb = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000.0 - - if (burst + 1) * dynb > tonumber(KEYS[4]) then - return {1, tostring(burst), tostring(dynr), tostring(dynb)} - end - - return {0, tostring(burst), tostring(dynr), tostring(dynb)} -]] -local bucket_check_id - - --- Updates a bucket --- KEYS[1] - prefix to update, e.g. RL_<triplet>_<seconds> --- KEYS[2] - current time in milliseconds --- KEYS[3] - dynamic rate multiplier --- KEYS[4] - dynamic burst multiplier --- KEYS[5] - max dyn rate (min: 1/x) --- KEYS[6] - max burst rate (min: 1/x) --- KEYS[7] - expire for a bucket --- Redis keys used: --- l - last hit --- b - current burst --- dr - current dynamic rate multiplier --- db - current dynamic burst multiplier -local bucket_update_script = [[ - local last = redis.call('HGET', KEYS[1], 'l') - local now = tonumber(KEYS[2]) - if not last then - -- New bucket - redis.call('HSET', KEYS[1], 'l', KEYS[2]) - redis.call('HSET', KEYS[1], 'b', '1') - redis.call('HSET', KEYS[1], 'dr', '10000') - redis.call('HSET', KEYS[1], 'db', '10000') - redis.call('EXPIRE', KEYS[1], KEYS[7]) - return {1, 1, 1} - end - - local burst = tonumber(redis.call('HGET', KEYS[1], 'b')) - local db = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000 - local dr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000 - - if dr < tonumber(KEYS[5]) and dr > 1.0 / tonumber(KEYS[5]) then - dr = dr * tonumber(KEYS[3]) - redis.call('HSET', KEYS[1], 'dr', tostring(math.floor(dr * 10000))) - end - - if db < tonumber(KEYS[6]) and db > 1.0 / tonumber(KEYS[6]) then - db = db * tonumber(KEYS[4]) - redis.call('HSET', KEYS[1], 'db', tostring(math.floor(db * 10000))) - end - - redis.call('HINCRBYFLOAT', KEYS[1], 'b', 1) - redis.call('HSET', KEYS[1], 'l', KEYS[2]) - redis.call('EXPIRE', KEYS[1], KEYS[7]) - - return {tostring(burst), tostring(dr), tostring(db)} -]] -local bucket_update_id - --- message_func(task, limit_type, prefix, bucket) -local message_func = function(_, limit_type, _, _) - return string.format('Ratelimit "%s" exceeded', limit_type) -end - -local rspamd_logger = require "rspamd_logger" -local rspamd_util = require "rspamd_util" -local rspamd_lua_utils = require "lua_util" -local lua_redis = require "lua_redis" -local fun = require "fun" -local lua_maps = require "lua_maps" -local lua_util = require "lua_util" -local rspamd_hash = require "rspamd_cryptobox_hash" - - -local function load_scripts(cfg, ev_base) - bucket_check_id = lua_redis.add_redis_script(bucket_check_script, redis_params) - bucket_update_id = lua_redis.add_redis_script(bucket_update_script, redis_params) -end - -local limit_parser -local function parse_string_limit(lim, no_error) - local function parse_time_suffix(s) - if s == 's' then - return 1 - elseif s == 'm' then - return 60 - elseif s == 'h' then - return 3600 - elseif s == 'd' then - return 86400 - end - end - local function parse_num_suffix(s) - if s == '' then - return 1 - elseif s == 'k' then - return 1000 - elseif s == 'm' then - return 1000000 - elseif s == 'g' then - return 1000000000 - end - end - local lpeg = require "lpeg" - - if not limit_parser then - local digit = lpeg.R("09") - limit_parser = {} - limit_parser.integer = - (lpeg.S("+-") ^ -1) * - (digit ^ 1) - limit_parser.fractional = - (lpeg.P(".") ) * - (digit ^ 1) - limit_parser.number = - (limit_parser.integer * - (limit_parser.fractional ^ -1)) + - (lpeg.S("+-") * limit_parser.fractional) - limit_parser.time = lpeg.Cf(lpeg.Cc(1) * - (limit_parser.number / tonumber) * - ((lpeg.S("smhd") / parse_time_suffix) ^ -1), - function (acc, val) return acc * val end) - limit_parser.suffixed_number = lpeg.Cf(lpeg.Cc(1) * - (limit_parser.number / tonumber) * - ((lpeg.S("kmg") / parse_num_suffix) ^ -1), - function (acc, val) return acc * val end) - limit_parser.limit = lpeg.Ct(limit_parser.suffixed_number * - (lpeg.S(" ") ^ 0) * lpeg.S("/") * (lpeg.S(" ") ^ 0) * - limit_parser.time) - end - local t = lpeg.match(limit_parser.limit, lim) - - if t and t[1] and t[2] and t[2] ~= 0 then - return t[2], t[1] - end - - if not no_error then - rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim) - end - - return nil -end - -local function parse_limit(name, data) - local buckets = {} - if type(data) == 'table' then - -- 3 cases here: - -- * old limit in format [burst, rate] - -- * vector of strings in Andrew's string format - -- * proper bucket table - if #data == 2 and tonumber(data[1]) and tonumber(data[2]) then - -- Old style ratelimit - rspamd_logger.warnx(rspamd_config, 'old style ratelimit for %s', name) - if tonumber(data[1]) > 0 and tonumber(data[2]) > 0 then - table.insert(buckets, { - burst = data[1], - rate = data[2] - }) - elseif data[1] ~= 0 then - rspamd_logger.warnx(rspamd_config, 'invalid numbers for %s', name) - else - rspamd_logger.infox(rspamd_config, 'disable limit %s, burst is zero', name) - end - else - -- Recursively map parse_limit and flatten the list - fun.each(function(l) - -- Flatten list - for _,b in ipairs(l) do table.insert(buckets, b) end - end, fun.map(function(d) return parse_limit(d, name) end, data)) - end - elseif type(data) == 'string' then - local rep_rate, burst = parse_string_limit(data) - - if rep_rate and burst then - table.insert(buckets, { - burst = burst, - rate = 1.0 / rep_rate -- reciprocal - }) - end - end - - -- Filter valid - return fun.totable(fun.filter(function(val) - return type(val.burst) == 'number' and type(val.rate) == 'number' - end, buckets)) -end - ---- Check whether this addr is bounce -local function check_bounce(from) - return fun.any(function(b) return b == from end, settings.bounce_senders) -end - -local keywords = { - ['ip'] = { - ['get_value'] = function(task) - local ip = task:get_ip() - if ip and ip:is_valid() then return tostring(ip) end - return nil - end, - }, - ['rip'] = { - ['get_value'] = function(task) - local ip = task:get_ip() - if ip and ip:is_valid() and not ip:is_local() then return tostring(ip) end - return nil - end, - }, - ['from'] = { - ['get_value'] = function(task) - local from = task:get_from(0) - if ((from or E)[1] or E).addr then - return string.lower(from[1]['addr']) - end - return nil - end, - }, - ['bounce'] = { - ['get_value'] = function(task) - local from = task:get_from(0) - if not ((from or E)[1] or E).user then - return '_' - end - if check_bounce(from[1]['user']) then return '_' else return nil end - end, - }, - ['asn'] = { - ['get_value'] = function(task) - local asn = task:get_mempool():get_variable('asn') - if not asn then - return nil - else - return asn - end - end, - }, - ['user'] = { - ['get_value'] = function(task) - local auser = task:get_user() - if not auser then - return nil - else - return auser - end - end, - }, - ['to'] = { - ['get_value'] = function(task) - return task:get_principal_recipient() - end, - }, -} - -local function gen_rate_key(task, rtype, bucket) - local key_t = {tostring(lua_util.round(100000.0 / bucket.burst))} - local key_keywords = lua_util.str_split(rtype, '_') - local have_user = false - - for _, v in ipairs(key_keywords) do - local ret - - if keywords[v] and type(keywords[v]['get_value']) == 'function' then - ret = keywords[v]['get_value'](task) - end - if not ret then return nil end - if v == 'user' then have_user = true end - if type(ret) ~= 'string' then ret = tostring(ret) end - table.insert(key_t, ret) - end - - if have_user and not task:get_user() then - return nil - end - - return table.concat(key_t, ":") -end - -local function make_prefix(redis_key, name, bucket) - local hash_len = 24 - if hash_len > #redis_key then hash_len = #redis_key end - local hash = settings.prefix .. - string.sub(rspamd_hash.create(redis_key):base32(), 1, hash_len) - -- Fill defaults - if not bucket.spam_factor_rate then - bucket.spam_factor_rate = settings.spam_factor_rate - end - if not bucket.ham_factor_rate then - bucket.ham_factor_rate = settings.ham_factor_rate - end - if not bucket.spam_factor_burst then - bucket.spam_factor_burst = settings.spam_factor_burst - end - if not bucket.ham_factor_burst then - bucket.ham_factor_burst = settings.ham_factor_burst - end - - return { - bucket = bucket, - name = name, - hash = hash - } -end - -local function limit_to_prefixes(task, k, v, prefixes) - local n = 0 - for _,bucket in ipairs(v) do - local prefix = gen_rate_key(task, k, bucket) - - if prefix then - prefixes[prefix] = make_prefix(prefix, k, bucket) - n = n + 1 - end - end - - return n -end - -local function ratelimit_cb(task) - if not settings.allow_local and - rspamd_lua_utils.is_rspamc_or_controller(task) then return end - - -- Get initial task data - local ip = task:get_from_ip() - if ip and ip:is_valid() and settings.whitelisted_ip then - if settings.whitelisted_ip:get_key(ip) then - -- Do not check whitelisted ip - rspamd_logger.infox(task, 'skip ratelimit for whitelisted IP') - return - end - end - -- Parse all rcpts - local rcpts = task:get_recipients() - local rcpts_user = {} - if rcpts then - fun.each(function(r) - fun.each(function(type) table.insert(rcpts_user, r[type]) end, {'user', 'addr'}) - end, rcpts) - - if fun.any(function(r) return settings.whitelisted_rcpts:get_key(r) end, rcpts_user) then - rspamd_logger.infox(task, 'skip ratelimit for whitelisted recipient') - return - end - end - -- Get user (authuser) - if settings.whitelisted_user then - local auser = task:get_user() - if settings.whitelisted_user:get_key(auser) then - rspamd_logger.infox(task, 'skip ratelimit for whitelisted user') - return - end - end - -- Now create all ratelimit prefixes - local prefixes = {} - local nprefixes = 0 - - for k,v in pairs(settings.limits) do - nprefixes = nprefixes + limit_to_prefixes(task, k, v, prefixes) - end - - for k, hdl in pairs(settings.custom_keywords or E) do - local ret, redis_key, bd = pcall(hdl, task) - - if ret then - local bucket = parse_limit(k, bd) - if bucket[1] then - prefixes[redis_key] = make_prefix(redis_key, k, bucket[1]) - end - nprefixes = nprefixes + 1 - else - rspamd_logger.errx(task, 'cannot call handler for %s: %s', - k, redis_key) - end - end - - local function gen_check_cb(prefix, bucket, lim_name) - return function(err, data) - if err then - rspamd_logger.errx('cannot check limit %s: %s %s', prefix, err, data) - elseif type(data) == 'table' and data[1] and data[1] == 1 then - -- set symbol only and do NOT soft reject - if settings.symbol then - task:insert_result(settings.symbol, 0.0, lim_name .. "(" .. prefix .. ")") - rspamd_logger.infox(task, - 'set_symbol_only: ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)', - lim_name, prefix, - bucket.burst, bucket.rate, - data[2], data[3], data[4]) - return - -- set INFO symbol and soft reject - elseif settings.info_symbol then - task:insert_result(settings.info_symbol, 1.0, - lim_name .. "(" .. prefix .. ")") - end - rspamd_logger.infox(task, - 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)', - lim_name, prefix, - bucket.burst, bucket.rate, - data[2], data[3], data[4]) - task:set_pre_result('soft reject', - message_func(task, lim_name, prefix, bucket)) - end - end - end - - -- Don't do anything if pre-result has been already set - if task:has_pre_result() then return end - - if nprefixes > 0 then - -- Save prefixes to the cache to allow update - task:cache_set('ratelimit_prefixes', prefixes) - local now = rspamd_util.get_time() - now = lua_util.round(now * 1000.0) -- Get milliseconds - -- Now call check script for all defined prefixes - - for pr,value in pairs(prefixes) do - local bucket = value.bucket - local rate = (bucket.rate) / 1000.0 -- Leak rate in messages/ms - rspamd_logger.debugm(N, task, "check limit %s:%s -> %s (%s/%s)", - value.name, pr, value.hash, bucket.burst, bucket.rate) - lua_redis.exec_redis_script(bucket_check_id, - {key = value.hash, task = task, is_write = true}, - gen_check_cb(pr, bucket, value.name), - {value.hash, tostring(now), tostring(rate), tostring(bucket.burst), - tostring(settings.expire)}) - end - end -end - -local function ratelimit_update_cb(task) - local prefixes = task:cache_get('ratelimit_prefixes') - - if prefixes then - if task:has_pre_result() then - -- Already rate limited/greylisted, do nothing - rspamd_logger.debugm(N, task, 'pre-action has been set, do not update') - return - end - - local is_spam = not (task:get_metric_action() == 'no action') - - -- Update each bucket - for k, v in pairs(prefixes) do - local bucket = v.bucket - local function update_bucket_cb(err, data) - if err then - rspamd_logger.errx(task, 'cannot update rate bucket %s: %s', - k, err) - else - rspamd_logger.debugm(N, task, - "updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s", - v.name, k, v.hash, - bucket.burst, bucket.rate, - data[1], data[2], data[3]) - end - end - local now = rspamd_util.get_time() - now = lua_util.round(now * 1000.0) -- Get milliseconds - local mult_burst = bucket.ham_factor_burst or 1.0 - local mult_rate = bucket.ham_factor_burst or 1.0 - - if is_spam then - mult_burst = bucket.spam_factor_burst or 1.0 - mult_rate = bucket.spam_factor_rate or 1.0 - end - - lua_redis.exec_redis_script(bucket_update_id, - {key = v.hash, task = task, is_write = true}, - update_bucket_cb, - {v.hash, tostring(now), tostring(mult_rate), tostring(mult_burst), - tostring(settings.max_rate_mult), tostring(settings.max_bucket_mult), - tostring(settings.expire)}) - end - end -end - -local opts = rspamd_config:get_all_opt(N) -if opts then - - settings = lua_util.override_defaults(settings, opts) - - if opts['limit'] then - rspamd_logger.errx(rspamd_config, 'Legacy ratelimit config format no longer supported') - end - - if opts['rates'] and type(opts['rates']) == 'table' then - -- new way of setting limits - fun.each(function(t, lim) - local buckets = parse_limit(t, lim) - - if buckets and #buckets > 0 then - settings.limits[t] = buckets - end - end, opts['rates']) - end - - local enabled_limits = fun.totable(fun.map(function(t) - return t - end, settings.limits)) - rspamd_logger.infox(rspamd_config, - 'enabled rate buckets: [%1]', table.concat(enabled_limits, ',')) - - -- Ret, ret, ret: stupid legacy stuff: - -- If we have a string with commas then load it as as static map - -- otherwise, apply normal logic of Rspamd maps - - local wrcpts = opts['whitelisted_rcpts'] - if type(wrcpts) == 'string' then - if string.find(wrcpts, ',') then - settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl( - lua_util.rspamd_str_split(wrcpts, ','), 'set', 'Ratelimit whitelisted rcpts') - else - settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set', - 'Ratelimit whitelisted rcpts') - end - elseif type(opts['whitelisted_rcpts']) == 'table' then - settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set', - 'Ratelimit whitelisted rcpts') - else - -- Stupid default... - settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl( - settings.whitelisted_rcpts, 'set', 'Ratelimit whitelisted rcpts') - end - - if opts['whitelisted_ip'] then - settings.whitelisted_ip = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_ip', 'radix', - 'Ratelimit whitelist ip map') - end - - if opts['whitelisted_user'] then - settings.whitelisted_user = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_user', 'set', - 'Ratelimit whitelist user map') - end - - settings.custom_keywords = {} - if opts['custom_keywords'] then - local ret, res_or_err = pcall(loadfile(opts['custom_keywords'])) - - if ret then - opts['custom_keywords'] = {} - if type(res_or_err) == 'table' then - for k,hdl in pairs(res_or_err) do - settings['custom_keywords'][k] = hdl - end - elseif type(res_or_err) == 'function' then - settings['custom_keywords']['custom'] = res_or_err - end - else - rspamd_logger.errx(rspamd_config, 'cannot execute %s: %s', - opts['custom_keywords'], res_or_err) - settings['custom_keywords'] = {} - end - end - - if opts['message_func'] then - message_func = assert(load(opts['message_func']))() - end - - redis_params = lua_redis.parse_redis_server('ratelimit') - - if not redis_params then - rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module') - lua_util.disable_module(N, "redis") - else - local s = { - type = 'prefilter,nostat', - name = 'RATELIMIT_CHECK', - priority = 7, - callback = ratelimit_cb, - flags = 'empty', - } - - if settings.symbol then - s.name = settings.symbol - elseif settings.info_symbol then - s.name = settings.info_symbol - end - - rspamd_config:register_symbol(s) - rspamd_config:register_symbol { - type = 'idempotent', - name = 'RATELIMIT_UPDATE', - callback = ratelimit_update_cb, - } - end -end - -rspamd_config:add_on_load(function(cfg, ev_base, worker) - load_scripts(cfg, ev_base) -end) diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 30a06d24..970ec252 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -7,46 +7,50 @@ ENV GOSU_VERSION 1.9 # Prerequisites RUN apt-get update && apt-get install -y --no-install-recommends \ - apt-transport-https \ - ca-certificates \ - cron \ - gnupg \ - mysql-client \ - supervisor \ - syslog-ng \ - syslog-ng-core \ - syslog-ng-mod-redis \ - dirmngr \ - netcat \ - psmisc \ - wget \ - patch \ - && rm -rf /var/lib/apt/lists/* \ - && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ - && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ - && chmod +x /usr/local/bin/gosu \ - && gosu nobody true + apt-transport-https \ + ca-certificates \ + cron \ + gettext \ + gnupg \ + mysql-client \ + rsync \ + supervisor \ + syslog-ng \ + syslog-ng-core \ + syslog-ng-mod-redis \ + dirmngr \ + netcat \ + psmisc \ + wget \ + patch \ + && rm -rf /var/lib/apt/lists/* \ + && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ + && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ + && chmod +x /usr/local/bin/gosu \ + && gosu nobody true RUN mkdir /usr/share/doc/sogo \ - && touch /usr/share/doc/sogo/empty.sh \ - && apt-key adv --keyserver keyserver.ubuntu.com --recv-key 0x810273C4 \ - && echo "deb http://packages.inverse.ca/SOGo/nightly/4/debian/ stretch stretch" > /etc/apt/sources.list.d/sogo.list \ - && apt-get update && apt-get install -y --force-yes \ - sogo \ - sogo-activesync \ - && rm -rf /var/lib/apt/lists/* \ - && echo '* * * * * sogo /usr/sbin/sogo-ealarms-notify 2>/dev/null' > /etc/cron.d/sogo \ - && echo '* * * * * sogo /usr/sbin/sogo-tool expire-sessions 60' >> /etc/cron.d/sogo \ - && echo '0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds' >> /etc/cron.d/sogo \ - && touch /etc/default/locale + && touch /usr/share/doc/sogo/empty.sh \ + && apt-key adv --keyserver keyserver.ubuntu.com --recv-key 0x810273C4 \ + && echo "deb http://packages.inverse.ca/SOGo/nightly/4/debian/ stretch stretch" > /etc/apt/sources.list.d/sogo.list \ + && apt-get update && apt-get install -y --force-yes \ + sogo \ + sogo-activesync \ + && rm -rf /var/lib/apt/lists/* \ + && echo '* * * * * sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/sieve.creds 2>/dev/null' > /etc/cron.d/sogo \ + && echo '* * * * * sogo /usr/sbin/sogo-tool expire-sessions 60' >> /etc/cron.d/sogo \ + && echo '0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds' >> /etc/cron.d/sogo \ + && touch /etc/default/locale -COPY ./bootstrap-sogo.sh / +COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY supervisord.conf /etc/supervisor/supervisord.conf -COPY theme-blue.js /usr/lib/GNUstep/SOGo/WebServerResources/js/theme-blue.js -COPY theme-blue.css /usr/lib/GNUstep/SOGo/WebServerResources/css/theme-default.css -COPY sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg COPY acl.diff /acl.diff +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh + +RUN chmod +x /bootstrap-sogo.sh \ + /usr/local/sbin/stop-supervisor.sh + CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf RUN rm -rf /tmp/* /var/tmp/* diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index 46d8ec6c..7f1835db 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -1,7 +1,7 @@ #!/bin/bash # Wait for MySQL to warm-up -while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do +while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for database to come up..." sleep 2 done @@ -13,20 +13,31 @@ do sleep 3 done +# Wait for updated schema +DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions;" -BN) +DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) +while [[ ${DBV_NOW} != ${DBV_NEW} ]]; do + echo "Waiting for schema update..." + DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions;" -BN) + DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) + sleep 5 +done +echo "DB schema is ${DBV_NOW}" + # Recreate view -mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view" +mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view" while [[ ${VIEW_OK} != 'OK' ]]; do - mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF -CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS -SELECT mailbox.username, mailbox.domain, mailbox.username, if(json_extract(attributes, '$.force_pw_update') LIKE '%0%', password, 'invalid'), mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox + mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF +CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, kind, multiple_bookings) AS +SELECT mailbox.username, mailbox.domain, mailbox.username, if(json_extract(attributes, '$.force_pw_update') LIKE '%0%', if(json_extract(attributes, '$.sogo_access') LIKE '%1%', password, 'invalid'), 'invalid'), mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gda.ad_alias, ''), mailbox.kind, mailbox.multiple_bookings FROM mailbox LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)') LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username WHERE mailbox.active = '1' GROUP BY mailbox.username; EOF - if [[ ! -z $(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then + if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then VIEW_OK=OK else echo "Will retry to setup SOGo view in 3s" @@ -37,11 +48,11 @@ done # Wait for static view table if missing after update and update content while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do - if [[ ! -z $(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then + if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then STATIC_VIEW_OK=OK echo "Updating _sogo_static_view content..." - mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view SELECT * from sogo_view" - mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')" + mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, kind, multiple_bookings) SELECT c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, kind, multiple_bookings from sogo_view;" + mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')" else echo "Waiting for database initialization..." sleep 3 @@ -50,10 +61,10 @@ done # Recreate password update trigger -mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password" +mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password" while [[ ${TRIGGER_OK} != 'OK' ]]; do - mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF + mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF DELIMITER - CREATE TRIGGER sogo_update_password AFTER UPDATE ON _sogo_static_view FOR EACH ROW @@ -63,7 +74,7 @@ END; - DELIMITER ; EOF - if [[ ! -z $(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then + if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then TRIGGER_OK=OK else echo "Will retry to setup SOGo password update trigger in 3s" @@ -72,28 +83,41 @@ EOF done -mkdir -p /var/lib/sogo/GNUstep/Defaults/ +if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + TRUST_PROXY="YES" +else + TRUST_PROXY="NO" +fi +# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl +RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9) # Generate plist header with timezone data +mkdir -p /var/lib/sogo/GNUstep/Defaults/ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//GNUstep//DTD plist 0.9//EN" "http://www.gnustep.org/plist-0_9.xml"> <plist version="0.9"> <dict> <key>OCSAclURL</key> - <string>mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_acl</string> + <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl</string> + <key>SOGoIMAPServer</key> + <string>imap://${IPV4_NETWORK}.250:143/?tls=YES</string> + <key>SOGoTrustProxyAuthentication</key> + <string>${TRUST_PROXY}</string> + <key>SOGoEncryptionKey</key> + <string>${RAND_PASS}</string> <key>OCSCacheFolderURL</key> - <string>mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_cache_folder</string> + <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder</string> <key>OCSEMailAlarmsFolderURL</key> - <string>mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_alarms_folder</string> + <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_alarms_folder</string> <key>OCSFolderInfoURL</key> - <string>mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_folder_info</string> + <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_folder_info</string> <key>OCSSessionsFolderURL</key> - <string>mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_sessions_folder</string> + <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_sessions_folder</string> <key>OCSStoreURL</key> - <string>mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_store</string> + <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_store</string> <key>SOGoProfileURL</key> - <string>mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_user_profile</string> + <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_user_profile</string> <key>SOGoTimeZone</key> <string>${TZ}</string> <key>domains</key> @@ -101,9 +125,9 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist EOF # Generate multi-domain setup -while read line - do - echo " <key>${line}</key> +while read -r line gal + do + echo " <key>${line}</key> <dict> <key>SOGoMailDomain</key> <string>${line}</string> @@ -126,11 +150,11 @@ while read line <key>canAuthenticate</key> <string>YES</string> <key>displayName</key> - <string>GAL</string> + <string>GAL ${line}</string> <key>id</key> <string>${line}</string> <key>isAddressBook</key> - <string>YES</string> + <string>${gal}</string> <key>type</key> <string>sql</string> <key>userPasswordAlgorithm</key> @@ -138,11 +162,14 @@ while read line <key>prependPasswordScheme</key> <string>YES</string> <key>viewURL</key> - <string>mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/_sogo_static_view</string> - </dict> - </array> + <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/_sogo_static_view</string> + </dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist + # Generate alternative LDAP authentication dict, when SQL authentication fails + # This will nevertheless read attributes from LDAP + line=${line} envsubst < /etc/sogo/plist_ldap >> /var/lib/sogo/GNUstep/Defaults/sogod.plist + echo " </array> </dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist -done < <(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain;" -B -N) +done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N) # Generate footer echo ' </dict> @@ -153,46 +180,24 @@ echo ' </dict> chown sogo:sogo -R /var/lib/sogo/ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist -# Prevent theme switching -sed -i \ - -e 's/eaf5e9/E3F2FD/g' \ - -e 's/cbe5c8/BBDEFB/g' \ - -e 's/aad6a5/90CAF9/g' \ - -e 's/88c781/64B5F6/g' \ - -e 's/66b86a/42A5F5/g' \ - -e 's/56b04c/2196F3/g' \ - -e 's/4da143/1E88E5/g' \ - -e 's/388e3c/1976D2/g' \ - -e 's/367d2e/1565C0/g' \ - -e 's/225e1b/0D47A1/g' \ - -e 's/fafafa/82B1FF/g' \ - -e 's/69f0ae/448AFF/g' \ - -e 's/00e676/2979ff/g' \ - -e 's/00c853/2962ff/g' \ - /usr/lib/GNUstep/SOGo/WebServerResources/js/Common/Common.app.js \ - /usr/lib/GNUstep/SOGo/WebServerResources/js/Common.js - -sed -i \ - -e 's/default: "900"/default: "700"/g' \ - -e 's/default: "500"/default: "700"/g' \ - -e 's/"hue-1": "400"/"hue-1": "500"/g' \ - -e 's/"hue-1": "A100"/"hue-1": "500"/g' \ - -e 's/"hue-2": "800"/"hue-2": "700"/g' \ - -e 's/"hue-2": "300"/"hue-2": "700"/g' \ - -e 's/"hue-3": "A700"/"hue-3": "A200"/' \ - -e 's/default:"900"/default:"700"/g' \ - -e 's/default:"500"/default:"700"/g' \ - -e 's/"hue-1":"400"/"hue-1":"500"/g' \ - -e 's/"hue-1":"A100"/"hue-1":"500"/g' \ - -e 's/"hue-2":"800"/"hue-2":"700"/g' \ - -e 's/"hue-2":"300"/"hue-2":"700"/g' \ - -e 's/"hue-3":"A700"/"hue-3":"A200"/' \ - /usr/lib/GNUstep/SOGo/WebServerResources/js/Common/Common.app.js \ - /usr/lib/GNUstep/SOGo/WebServerResources/js/Common.js - -# Patch ACLs (comment this out to enable any or authenticated targets for ACL) -if patch -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then - patch /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; +# Patch ACLs +if [[ ${ACL_ANYONE} == 'allow' ]]; then + #enable any or authenticated targets for ACL + if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then + patch -R /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; + fi +else + #disable any or authenticated targets for ACL + if patch -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then + patch /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; + fi fi +# Copy logo, if any +[[ -f /etc/sogo/sogo-full.svg ]] && cp /etc/sogo/sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg + +# Rsync web content +echo "Syncing web content with named volume" +rsync -a /usr/lib/GNUstep/SOGo/. /sogo_web/ + exec gosu sogo /usr/sbin/sogod diff --git a/data/Dockerfiles/sogo/sogo-full.svg b/data/Dockerfiles/sogo/sogo-full.svg deleted file mode 100644 index 281ee7c6..00000000 --- a/data/Dockerfiles/sogo/sogo-full.svg +++ /dev/null @@ -1,160 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - version="1.1" - id="Layer_1" - x="0px" - y="0px" - width="640px" - height="350px" - viewBox="78.712 58.488 640 350" - style="enable-background:new 78.712 58.488 640 350;" - xml:space="preserve" - inkscape:version="0.91 r13725" - sodipodi:docname="sogo-full.svg"><metadata - id="metadata9"><rdf:RDF><cc:Work - rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs - id="defs7" /><sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1721" - inkscape:window-height="1177" - id="namedview5" - showgrid="false" - inkscape:zoom="0.8396893" - inkscape:cx="360.23913" - inkscape:cy="334.02085" - inkscape:window-x="-8" - inkscape:window-y="-8" - inkscape:window-maximized="1" - inkscape:current-layer="Layer_1" /><path - style="fill:#1976d2;fill-opacity:0.71428573" - d="M648.541,145.679c-9.947,0-17.009-7.278-17.009-17.048c0-9.777,7.062-17.057,17.009-17.057 c10.024,0,17.086,7.279,17.086,17.057C665.627,138.401,658.565,145.679,648.541,145.679z M648.511,94.893 c-19.693,0-33.679,14.4-33.679,33.738c0,19.33,13.985,33.729,33.679,33.729c19.822,0,33.808-14.4,33.808-33.729 C682.318,109.293,668.333,94.893,648.511,94.893z M648.482,179.843c-29.889,0-51.123-21.868-51.123-51.212 c0-29.353,21.234-51.209,51.123-51.209c30.082,0,51.307,21.856,51.307,51.209C699.789,157.975,678.564,179.843,648.482,179.843z M648.442,58.488c-40.929,0-69.995,29.946-69.995,70.143c0,40.189,29.066,70.125,69.995,70.125c41.194,0,70.27-29.937,70.27-70.125 C718.712,88.434,689.637,58.488,648.442,58.488z M158.166,183.902l-21.018-5.008c-19.131-4.396-28.849-9.413-28.849-23.21 c0-15.684,15.99-21.965,30.419-21.965c14.667,0,25.382,7.329,31.693,18.737c0.02,0.048,0.051,0.097,0.09,0.157 c0.127,0.247,0.276,0.484,0.403,0.731l0.03-0.02c1.985,3.002,5.323,5.008,8.919,5.008c6.122,0,10.558-4.425,10.558-10.547 c0-2.341-0.504-4.82-1.601-6.688c-10.764-18.302-28.513-26.192-48.838-26.192c-27.594,0-54.262,13.797-54.262,44.218 c0,27.921,27.605,36.079,37.64,38.578l20.069,4.71c15.368,3.763,27.912,8.791,27.912,23.517c0,16.938-17.561,23.943-34.499,23.943 c-17.245,0-30.015-9.37-38.814-22.37h-0.01c-1.956-3-4.988-4.328-8.702-4.328c-5.984,0-10.805,5.185-10.587,11.162 c0.098,2.438,0.909,4.637,2.153,6.405c13.787,20.633,33.728,28.41,55.96,28.41c28.543,0,57.085-13.143,57.085-45.132 C193.918,203.325,178.551,188.613,158.166,183.902z M298.479,250.312c-33.866,0-55.199-25.403-55.199-58.331 c0-32.939,21.333-58.343,55.199-58.343c34.192,0,55.516,25.403,55.516,58.343C353.996,224.91,332.672,250.312,298.479,250.312z M298.479,114.823c-45.471,0-77.777,32.93-77.777,77.158c0,44.217,32.306,77.146,77.777,77.146 c45.786,0,78.093-32.929,78.093-77.146C376.572,147.753,344.266,114.823,298.479,114.823z M518.715,234.312 c-0.771,0.74-1.549,1.472-2.399,2.175c-1.106,1.014-2.391,2.112-3.854,3.208c-8.829,6.391-19.979,10.094-33.017,10.094 c-33.876,0-55.198-25.402-55.198-58.332c0-32.939,21.322-58.342,55.198-58.342c34.183,0,55.506,25.403,55.506,58.342 C534.951,208.653,529.135,223.774,518.715,234.312z M468.097,317.938c2.528,0,5.146-0.168,7.863-0.504 c5.018-0.631,9.588-0.909,13.729-0.909c19.24,0.109,29.036,5.7,34.943,12.158c5.895,6.499,8.168,15.311,8.158,22.796 c0.01,3.586-0.555,6.795-1.177,8.721c-2.944,8.93-8.888,15.002-17.996,19.576c-9.035,4.484-21.095,6.777-33.707,6.757 c-4.514,0-9.105-0.288-13.639-0.831c-8.573-0.987-19.911-4.671-28.13-11.093c-4.138-3.199-6.458-6.991-8.858-11.485 c-2.379-4.514-2.783-9.748-2.783-16.442v-0.742c0-12.346,4.84-20.544,11.051-26.5c3.07-2.904,5.69-5.064,7.99-6.438 c0.366-0.218,0.438-0.416,0.755-0.593C452.39,316.014,459.684,317.968,468.097,317.938z M479.445,114.301 c-45.471,0-77.786,32.929-77.786,77.157c0,29.887,14.765,54.598,38.378,67.489c-0.314,0.314-0.621,0.641-0.916,0.966 c-6.104,6.687-9.226,15.25-9.236,23.913c-0.008,3.821,0.624,7.741,1.977,11.494c-3.062,1.956-6.717,4.634-10.46,8.147 c-9.026,8.408-18.734,22.541-19.021,42.097c-0.01,0.454-0.01,0.829-0.01,1.118c-0.01,10.071,2.379,19.157,6.459,26.774 c6.133,11.466,15.683,19.445,25.539,24.77c9.917,5.334,20.257,8.166,29.273,9.274c5.373,0.643,10.826,0.988,16.268,0.988 c15.151-0.02,30.261-2.578,43.409-9.019c13.085-6.34,24.333-17.253,29.192-32.562c1.443-4.553,2.212-9.719,2.231-15.428 c-0.02-11.595-3.349-25.759-13.767-37.452c-10.421-11.734-27.654-19.566-51.288-19.459c-5.138,0-10.606,0.356-16.426,1.078 c-1.877,0.227-3.596,0.334-5.166,0.334c-7.239-0.048-10.872-2.053-13.036-4.098c-2.133-2.084-3.2-4.839-3.229-8.058 c-0.01-3.28,1.284-6.727,3.467-9.078c2.231-2.332,5.008-3.91,9.846-3.97c0.436,0,0.9,0.01,1.374,0.05 c3.101,0.216,6.112,0.325,9.037,0.325c24.188,0.047,42.38-7.448,54.756-17.759c12.415-10.312,18.971-22.854,22.071-32.76l-0.04-0.01 c3.37-8.899,5.197-18.715,5.197-29.166C557.539,147.229,525.234,114.301,479.445,114.301z" - id="path3" /><g - id="g3" - transform="matrix(0.69327133,0,0,0.69327133,-230.59227,-153.05511)"><g - id="g5"><g - id="g7"><path - d="m 748.616,546.705 c -6.272,4.929 9.576,36.937 20.52,33.516 10.944,-3.42 -10.945,-41.04 -20.52,-33.516 z" - id="path19" - inkscape:connector-curvature="0" - style="fill:#acef29" /></g></g><g - id="g37"><g - id="g39"><g - id="g41" /></g><g - id="g45"><g - id="g47"><g - id="g49" /></g></g></g><g - id="g57"><g - id="g59"><g - id="g61" /></g><g - id="g65"><g - id="g67"><g - id="g69" /></g></g></g><g - id="g73" - style="opacity:0.38999999"><g - id="g75" /></g><g - id="g81" /><g - id="g85" /><g - id="g99"><polyline - points="690.928,453.92 678.401,490.532 689.894,539.76 710.135,559.116 " - id="polyline101" - style="fill:#3d5263" /><g - id="g103"><g - id="g105"><polyline - points="665.082,423.023 677.433,450.19 693.064,457.2 715.139,427.604 " - id="polyline107" - style="fill:#fef3df" /><g - id="g109"><path - d="m 705.288,438.868 c 0,0 -17.599,-18.89 -38.165,-13.309 0,0 4.277,25.767 36.096,32.372 l 2.164,6.413 c 0,0 -49.958,-5.925 -46.456,-49.964 0,0 36.728,-16.138 55.372,9.881" - id="path111" - inkscape:connector-curvature="0" - style="fill:#b58765" /><polyline - points="855.735,417.576 844.957,445.404 829.753,453.295 806.023,425.008 " - id="polyline113" - style="fill:#fef3df" /><path - d="m 816.503,435.691 c 0,0 16.491,-19.864 37.34,-15.466 0,0 -2.797,25.969 -34.187,34.38 l -1.796,6.527 c 0,0 49.537,-8.768 43.528,-52.535 0,0 -37.588,-14.015 -54.719,13.026" - id="path115" - inkscape:connector-curvature="0" - style="fill:#b58765" /></g></g><path - d="m 721.173,570.346 42.418,-1.212 15.59845,-160.5887 c -42.319,1.209 -92.18245,30.1357 -91.14645,66.4047 0.031,1.102 0.125,2.111 0.182,3.163 0.181,2.98 0.504,5.741 0.89,8.424 1.602,11.197 4.722,20.488 7.355,32.71 1.27,5.906 4.299,17.614 4.299,17.614 0.052,0.308 0.143,0.606 0.196,0.913 2.281,12.491 9.666,24.028 20.208,32.572 z" - id="path117" - inkscape:connector-curvature="0" - style="fill:#b58765" - sodipodi:nodetypes="cccccccccc" /><path - d="m 758.532,407.21 4.626,161.937 42.418,-1.212 c 10.038,-9.132 16.75,-21.072 18.317,-33.672 0.035,-0.31 0.107,-0.613 0.141,-0.923 0,0 2.354,-11.862 3.287,-17.831 1.932,-12.352 4.518,-21.807 5.478,-33.075 0.23,-2.702 0.393,-5.477 0.4,-8.463 0.002,-1.053 0.036,-2.066 0.005,-3.168 -1.037,-36.269 -32.35,-64.802 -74.672,-63.593 z" - id="path119" - inkscape:connector-curvature="0" - style="fill:#b58765" /><g - id="g121"><g - id="g123"><path - d="m 822.293,541.988 c 0.613,21.473 -25.496,39.648 -58.32,40.586 -32.83,0.938 -59.932,-15.717 -60.546,-37.19 -0.614,-21.477 25.494,-39.648 58.324,-40.586 32.825,-0.938 59.929,15.713 60.542,37.19 z" - id="path125" - inkscape:connector-curvature="0" - style="fill:#fef3df" /></g></g><g - id="g127"><g - id="g129"><g - id="g131"><path - d="m 735.761,538.45 c 0.135,4.712 -3.578,8.644 -8.294,8.778 -4.708,0.134 -8.641,-3.579 -8.776,-8.291 -0.135,-4.718 3.58,-8.644 8.288,-8.779 4.717,-0.134 8.647,3.573 8.782,8.292 z" - id="path133" - inkscape:connector-curvature="0" - style="fill:#5a3620" /></g></g><g - id="g135"><g - id="g137"><path - d="m 806.891,536.418 c 0.135,4.712 -3.575,8.644 -8.291,8.778 -4.714,0.135 -8.646,-3.579 -8.781,-8.291 -0.135,-4.718 3.579,-8.644 8.293,-8.779 4.716,-0.134 8.644,3.573 8.779,8.292 z" - id="path139" - inkscape:connector-curvature="0" - style="fill:#5a3620" /></g></g></g><g - id="g141"><path - d="m 831.225,475.208 c 0.201,-2.368 0.344,-4.799 0.352,-7.411 0,-0.924 0.031,-1.81 0.003,-2.778 -0.883,-30.924 -26.904,-55.418 -62.478,-55.716 l -5.561,0.163 -0.005,0 c -0.005,0.014 -19.18666,70.69971 61.71134,107.73071 0.624,-3.294 9.87079,-9.74237 10.28979,-12.41137 1.694,-10.822 -5.15313,-19.70834 -4.31213,-29.57734 z" - id="path143" - inkscape:connector-curvature="0" - style="fill:#87654a" - sodipodi:nodetypes="ccccccccc" /></g><g - id="g145"><g - id="g147"><g - id="g149"><g - id="g151"><path - d="m 807.344,471.221 c 0.151,5.28 -4.011,9.684 -9.294,9.835 -5.279,0.151 -9.686,-4.008 -9.837,-9.288 -0.151,-5.285 4.011,-9.687 9.291,-9.838 5.282,-0.151 9.689,4.006 9.84,9.291 z" - id="path153" - inkscape:connector-curvature="0" - style="fill:#5a3620" /></g></g></g><g - id="g155"><g - id="g157"><g - id="g159"><path - d="m 737.68,473.211 c 0.151,5.28 -4.01,9.684 -9.289,9.835 -5.284,0.151 -9.685,-4.008 -9.835,-9.288 -0.151,-5.285 4.005,-9.687 9.289,-9.837 5.279,-0.152 9.684,4.005 9.835,9.29 z" - id="path161" - inkscape:connector-curvature="0" - style="fill:#5a3620" /></g></g></g><g - id="g163"><g - id="g165"><path - d="m 735.112,470.41 c 0.055,1.939 -1.47,3.555 -3.41,3.61 -1.939,0.055 -3.558,-1.47 -3.613,-3.41 -0.055,-1.935 1.474,-3.552 3.413,-3.607 1.94,-0.055 3.555,1.472 3.61,3.407 z" - id="path167" - inkscape:connector-curvature="0" - style="fill:#ffffff" /></g></g><g - id="g169"><g - id="g171"><path - d="m 804.125,468.439 c 0.055,1.939 -1.472,3.555 -3.409,3.61 -1.94,0.055 -3.556,-1.47 -3.611,-3.41 -0.055,-1.935 1.471,-3.552 3.411,-3.607 1.937,-0.056 3.554,1.471 3.609,3.407 z" - id="path173" - inkscape:connector-curvature="0" - style="fill:#ffffff" /></g></g></g></g><path - d="m 761.738,405.962 c -50.342,1.438 -89.989,43.417 -88.55,93.759 1.438,50.342 43.417,89.987 93.758,88.549 50.343,-1.438 89.988,-43.415 88.55,-93.757 -1.438,-50.343 -43.415,-89.989 -93.758,-88.551 z m -4.396,163.807 c -40.192,1.148 -73.725,-31.172 -74.896,-72.19 -1.172,-41.017 30.461,-75.2 70.653,-76.348 40.191,-1.148 73.723,31.172 74.895,72.19 1.171,41.018 -30.462,75.2 -70.652,76.348 z" - id="path179" - inkscape:connector-curvature="0" - style="fill:#f1f2f2" /><g - id="g181" /></g></g></svg> \ No newline at end of file diff --git a/data/Dockerfiles/sogo/stop-supervisor.sh b/data/Dockerfiles/sogo/stop-supervisor.sh new file mode 100755 index 00000000..5394490c --- /dev/null +++ b/data/Dockerfiles/sogo/stop-supervisor.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin diff --git a/data/Dockerfiles/sogo/supervisord.conf b/data/Dockerfiles/sogo/supervisord.conf index 2a889560..551a8e12 100644 --- a/data/Dockerfiles/sogo/supervisord.conf +++ b/data/Dockerfiles/sogo/supervisord.conf @@ -16,13 +16,6 @@ command=/usr/sbin/cron -f autorestart=true priority=2 -[program:sogo-webres] -command=/usr/bin/python -u -m SimpleHTTPServer 9192 -directory=/usr/lib/GNUstep/SOGo/ -user=sogo -autorestart=true -priority=4 - [program:bootstrap-sogo] command=/bootstrap-sogo.sh stdout_logfile=/dev/stdout @@ -33,3 +26,7 @@ priority=3 startretries=10 autorestart=true stopwaitsecs=120 + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/sogo/theme-blue.css b/data/Dockerfiles/sogo/theme-blue.css deleted file mode 100644 index 1282d405..00000000 --- a/data/Dockerfiles/sogo/theme-blue.css +++ /dev/null @@ -1,16 +0,0 @@ -md-autocomplete.md-default-theme input, md-autocomplete input{color:rgba(0,0,0,0.87)}.md-autocomplete-suggestions-container.md-default-theme li, .md-autocomplete-suggestions-container li{color:rgba(0,0,0,0.87)}md-bottom-sheet.md-default-theme.md-list md-list-item, md-bottom-sheet.md-list md-list-item{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-primary, .md-button.md-primary{color:rgb(25,118,210)}.md-button.md-default-theme.md-primary.md-fab, .md-button.md-primary.md-fab,.md-button.md-default-theme.md-primary.md-raised, .md-button.md-primary.md-raised{color:rgba(255,255,255,0.87);background-color:rgb(25,118,210)}.md-button.md-default-theme.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-primary.md-raised:not([disabled]) md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-primary.md-fab:not([disabled]):hover, .md-button.md-primary.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-primary.md-raised:not([disabled]):hover, .md-button.md-primary.md-raised:not([disabled]):hover{background-color:rgb(30,136,229)}.md-button.md-default-theme.md-primary:not([disabled]) md-icon, .md-button.md-primary:not([disabled]) md-icon{color:rgb(25,118,210)}._md a.md-default-theme:not(.md-button).md-primary, ._md a:not(.md-button).md-primary{color:rgb(25,118,210)}._md a.md-default-theme:not(.md-button).md-primary:hover, ._md a:not(.md-button).md-primary:hover{color:rgb(25,118,210)}md-card.md-default-theme .md-card-image, md-card .md-card-image{border-radius:2px 2px 0 0}md-card.md-default-theme md-card-header md-card-header-text .md-subhead, md-card md-card-header md-card-header-text .md-subhead,md-card.md-default-theme md-card-title md-card-title-text:not(:only-child) .md-subhead, md-card md-card-title md-card-title-text:not(:only-child) .md-subhead{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme .md-ink-ripple, md-checkbox .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not(.md-checked) .md-icon, md-checkbox:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-primary .md-ripple, md-checkbox:not([disabled]).md-primary .md-ripple{color:rgb(30,136,229)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked .md-ripple, md-checkbox:not([disabled]).md-primary.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme:not([disabled]).md-primary .md-ink-ripple, md-checkbox:not([disabled]).md-primary .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-checkbox:not([disabled]).md-primary.md-checked .md-ink-ripple{color:rgba(25,118,210,0.87)}md-checkbox.md-default-theme:not([disabled]).md-primary:not(.md-checked) .md-icon, md-checkbox:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked .md-icon, md-checkbox:not([disabled]).md-primary.md-checked .md-icon{background-color:rgba(25,118,210,0.87)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked.md-focused .md-container:before, md-checkbox:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:rgba(25,118,210,0.26)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked .md-icon:after, md-checkbox:not([disabled]).md-primary.md-checked .md-icon:after{border-color:rgba(255,255,255,0.87)}md-checkbox.md-default-theme:not([disabled]).md-primary .md-indeterminate[disabled] .md-container, md-checkbox:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme[disabled]:not(.md-checked) .md-icon, md-checkbox[disabled]:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme[disabled] .md-icon:after, md-checkbox[disabled] .md-icon:after{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme[disabled] .md-label, md-checkbox[disabled] .md-label{color:rgba(0,0,0,0.38)}md-chips.md-default-theme .md-chips, md-chips .md-chips{box-shadow:0 1px rgba(0,0,0,0.12)}md-chips.md-default-theme .md-chips.md-focused, md-chips .md-chips.md-focused{box-shadow:0 2px rgb(25,118,210)}md-chips.md-default-theme .md-chips .md-chip-input-container input, md-chips .md-chips .md-chip-input-container input{color:rgba(0,0,0,0.87)}md-chips.md-default-theme .md-chips .md-chip-input-container input:-moz-placeholder, md-chips .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-default-theme .md-chips .md-chip-input-container input::-moz-placeholder, md-chips .md-chips .md-chip-input-container input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme .md-chips .md-chip-input-container input:-ms-input-placeholder, md-chips .md-chips .md-chip-input-container input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme .md-chips .md-chip-input-container input::-webkit-input-placeholder, md-chips .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme md-chip.md-focused, md-chips md-chip.md-focused{background:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-chips.md-default-theme md-chip.md-focused md-icon, md-chips md-chip.md-focused md-icon{color:rgba(255,255,255,0.87)}.md-default-theme .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator, .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid rgb(33,150,243)}.md-default-theme .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled, .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:rgba(33,150,243,0.6)}.md-default-theme .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-default-theme .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:rgb(33,150,243);color:rgba(0,0,0,0.87);border-color:transparent}.md-default-theme .md-calendar-date-disabled, .md-calendar-date-disabled,.md-default-theme .md-calendar-month-label-disabled, .md-calendar-month-label-disabled{color:rgba(0,0,0,0.38)}.md-default-theme .md-calendar-month-label md-icon, .md-calendar-month-label md-icon,.md-default-theme .md-datepicker-input, .md-datepicker-input{color:rgba(0,0,0,0.87)}.md-default-theme .md-datepicker-input:-moz-placeholder, .md-datepicker-input:-moz-placeholder,.md-default-theme .md-datepicker-input::-moz-placeholder, .md-datepicker-input::-moz-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme .md-datepicker-input:-ms-input-placeholder, .md-datepicker-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme .md-datepicker-input::-webkit-input-placeholder, .md-datepicker-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme .md-datepicker-input-container, .md-datepicker-input-container{border-bottom-color:rgba(0,0,0,0.12)}.md-default-theme .md-datepicker-input-container.md-datepicker-focused, .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(25,118,210)}.md-default-theme .md-datepicker-triangle-button .md-datepicker-expand-triangle, .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:rgba(0,0,0,0.54)}.md-default-theme .md-datepicker-open .md-datepicker-calendar-icon, .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(25,118,210)}md-dialog.md-default-theme.md-content-overflow .md-actions, md-dialog.md-content-overflow .md-actions,md-dialog.md-default-theme.md-content-overflow md-dialog-actions, md-dialog.md-content-overflow md-dialog-actions,md-divider.md-default-theme, md-divider{border-top-color:rgba(0,0,0,0.12)}.layout-gt-lg-row>md-divider.md-default-theme, .layout-gt-lg-row>md-divider,.layout-gt-md-row>md-divider.md-default-theme, .layout-gt-md-row>md-divider,.layout-gt-sm-row>md-divider.md-default-theme, .layout-gt-sm-row>md-divider,.layout-gt-xs-row>md-divider.md-default-theme, .layout-gt-xs-row>md-divider,.layout-lg-row>md-divider.md-default-theme, .layout-lg-row>md-divider,.layout-md-row>md-divider.md-default-theme, .layout-md-row>md-divider,.layout-row>md-divider.md-default-theme, .layout-row>md-divider,.layout-sm-row>md-divider.md-default-theme, .layout-sm-row>md-divider,.layout-xl-row>md-divider.md-default-theme, .layout-xl-row>md-divider,.layout-xs-row>md-divider.md-default-theme, .layout-xs-row>md-divider{border-right-color:rgba(0,0,0,0.12)}md-icon.md-default-theme, md-icon{color:rgba(0,0,0,0.54)}md-icon.md-default-theme.md-primary, md-icon.md-primary{color:rgb(25,118,210)}md-input-container.md-default-theme .md-input, md-input-container .md-input{color:rgba(0,0,0,0.87);border-color:rgba(0,0,0,0.12)}md-input-container.md-default-theme .md-input:-moz-placeholder, md-input-container .md-input:-moz-placeholder,md-input-container.md-default-theme .md-input::-moz-placeholder, md-input-container .md-input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme .md-input:-ms-input-placeholder, md-input-container .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme .md-input::-webkit-input-placeholder, md-input-container .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme>md-icon, md-input-container>md-icon{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme .md-placeholder, md-input-container .md-placeholder,md-input-container.md-default-theme label, md-input-container label{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme:not(.md-input-focused):not(.md-input-invalid) label.md-required:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme .md-input-message-animation .md-char-counter, md-input-container .md-input-message-animation .md-char-counter,md-input-container.md-default-theme .md-input-messages-animation .md-char-counter, md-input-container .md-input-messages-animation .md-char-counter{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-input-focused .md-input:-moz-placeholder, md-input-container.md-input-focused .md-input:-moz-placeholder,md-input-container.md-default-theme.md-input-focused .md-input::-moz-placeholder, md-input-container.md-input-focused .md-input::-moz-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-input-focused .md-input:-ms-input-placeholder, md-input-container.md-input-focused .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-input-focused .md-input::-webkit-input-placeholder, md-input-container.md-input-focused .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-has-value label, md-input-container:not(.md-input-invalid).md-input-has-value label{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused .md-input, md-input-container:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-default-theme:not(.md-input-invalid).md-input-resized .md-input, md-input-container:not(.md-input-invalid).md-input-resized .md-input{border-color:rgb(25,118,210)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused label, md-input-container:not(.md-input-invalid).md-input-focused label,md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused md-icon, md-input-container:not(.md-input-invalid).md-input-focused md-icon{color:rgb(25,118,210)}md-list.md-default-theme md-list-item.md-2-line .md-list-item-text h3, md-list md-list-item.md-2-line .md-list-item-text h3,md-list.md-default-theme md-list-item.md-2-line .md-list-item-text h4, md-list md-list-item.md-2-line .md-list-item-text h4,md-list.md-default-theme md-list-item.md-3-line .md-list-item-text h3, md-list md-list-item.md-3-line .md-list-item-text h3,md-list.md-default-theme md-list-item.md-3-line .md-list-item-text h4, md-list md-list-item.md-3-line .md-list-item-text h4{color:rgba(0,0,0,0.87)}md-list.md-default-theme md-list-item.md-2-line .md-list-item-text p, md-list md-list-item.md-2-line .md-list-item-text p,md-list.md-default-theme md-list-item.md-3-line .md-list-item-text p, md-list md-list-item.md-3-line .md-list-item-text p{color:rgba(0,0,0,0.54)}md-list.md-default-theme md-list-item>md-icon, md-list md-list-item>md-icon{color:rgba(0,0,0,0.54)}md-list.md-default-theme md-list-item>md-icon.md-highlight, md-list md-list-item>md-icon.md-highlight{color:rgb(25,118,210)}md-menu-content.md-default-theme md-menu-item, md-menu-content md-menu-item{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme md-menu-item md-icon, md-menu-content md-menu-item md-icon{color:rgba(0,0,0,0.54)}md-menu-content.md-default-theme md-menu-item .md-button[disabled], md-menu-content md-menu-item .md-button[disabled],md-menu-content.md-default-theme md-menu-item .md-button[disabled] md-icon, md-menu-content md-menu-item .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}md-menu-bar.md-default-theme>button.md-button, md-menu-bar>button.md-button{color:rgba(0,0,0,0.87);border-radius:2px}md-menu-bar.md-default-theme md-menu>button, md-menu-bar md-menu>button{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme .md-menu>.md-button:after, md-menu-content .md-menu>.md-button:after{color:rgba(0,0,0,0.54)}md-toolbar.md-default-theme.md-menu-toolbar md-toolbar-filler, md-toolbar.md-menu-toolbar md-toolbar-filler{background-color:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme.md-menu-toolbar md-toolbar-filler md-icon, md-toolbar.md-menu-toolbar md-toolbar-filler md-icon{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme .md-button._md-nav-button.md-unselected, md-nav-bar .md-button._md-nav-button.md-unselected{color:rgba(0,0,0,0.54)}md-nav-bar.md-default-theme .md-button._md-nav-button[disabled], md-nav-bar .md-button._md-nav-button[disabled]{color:rgba(0,0,0,0.38)}md-nav-bar.md-default-theme.md-primary>.md-nav-bar, md-nav-bar.md-primary>.md-nav-bar{background-color:rgb(25,118,210)}md-nav-bar.md-default-theme.md-primary>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-primary>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-nav-bar.md-default-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar, md-toolbar>md-nav-bar>.md-nav-bar{background-color:rgb(25,118,210)}md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button, md-toolbar>md-nav-bar>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-progress-circular.md-default-theme path, md-progress-circular path{stroke:rgb(25,118,210)}md-progress-linear.md-default-theme .md-container, md-progress-linear .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme .md-bar, md-progress-linear .md-bar{background-color:rgb(25,118,210)}md-progress-linear.md-default-theme[md-mode=buffer].md-primary .md-bar1, md-progress-linear[md-mode=buffer].md-primary .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme[md-mode=buffer].md-primary .md-dashed:before, md-progress-linear[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme .md-off, md-radio-button .md-off{border-color:rgba(0,0,0,0.54)}md-radio-button.md-default-theme:not([disabled]).md-primary .md-on, md-radio-button:not([disabled]).md-primary .md-on,md-radio-button.md-default-theme:not([disabled]) .md-primary .md-on, md-radio-button:not([disabled]) .md-primary .md-on,md-radio-group.md-default-theme:not([disabled]).md-primary .md-on, md-radio-group:not([disabled]).md-primary .md-on,md-radio-group.md-default-theme:not([disabled]) .md-primary .md-on, md-radio-group:not([disabled]) .md-primary .md-on{background-color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme:not([disabled]).md-primary.md-checked .md-off, md-radio-button:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-default-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-button:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-default-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-button:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-default-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-button:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-default-theme:not([disabled]).md-primary.md-checked .md-off, md-radio-group:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-default-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-group:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-default-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-group:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-default-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-group:not([disabled]) .md-primary .md-checked .md-off{border-color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-group:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme:not([disabled]).md-primary .md-container .md-ripple, md-radio-button:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-default-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-default-theme:not([disabled]).md-primary .md-container .md-ripple, md-radio-group:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-default-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group:not([disabled]) .md-primary .md-container .md-ripple{color:rgb(30,136,229)}md-radio-button.md-default-theme[disabled], md-radio-button[disabled],md-radio-group.md-default-theme[disabled], md-radio-group[disabled]{color:rgba(0,0,0,0.38)}md-radio-button.md-default-theme[disabled] .md-container .md-off, md-radio-button[disabled] .md-container .md-off,md-radio-button.md-default-theme[disabled] .md-container .md-on, md-radio-button[disabled] .md-container .md-on,md-radio-group.md-default-theme[disabled] .md-container .md-off, md-radio-group[disabled] .md-container .md-off,md-radio-group.md-default-theme[disabled] .md-container .md-on, md-radio-group[disabled] .md-container .md-on{border-color:rgba(0,0,0,0.38)}md-radio-group.md-default-theme .md-checked:not([disabled]).md-primary .md-ink-ripple, md-radio-group .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-default-theme.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:rgba(25,118,210,0.26)}md-radio-group.md-default-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before, md-radio-group.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-default-theme.md-focused:not(:empty).md-primary .md-checked .md-container:before, md-radio-group.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:rgba(25,118,210,0.26)}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-default-theme .md-select-value span:first-child:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select .md-select-value span:first-child:after{color:rgba(0,0,0,0.38)}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme .md-select-value, md-input-container.md-input-focused:not(.md-input-has-value) md-select .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme .md-select-value.md-select-placeholder, md-input-container.md-input-focused:not(.md-input-has-value) md-select .md-select-value.md-select-placeholder{color:rgb(25,118,210)}md-input-container.md-input-invalid md-select.md-default-theme.md-no-underline .md-select-value, md-input-container.md-input-invalid md-select.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme .md-select-value, md-select .md-select-value{border-bottom-color:rgba(0,0,0,0.12)}md-select.md-default-theme .md-select-value.md-select-placeholder, md-select .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-no-underline .md-select-value, md-select.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.ng-invalid.ng-touched.md-no-underline .md-select-value, md-select.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme:not([disabled]):focus .md-select-value, md-select:not([disabled]):focus .md-select-value{border-bottom-color:rgb(25,118,210);color:rgba(0,0,0,0.87)}md-select.md-default-theme:not([disabled]):focus .md-select-value.md-select-placeholder, md-select:not([disabled]):focus .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.87)}md-select.md-default-theme:not([disabled]):focus.md-no-underline .md-select-value, md-select:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme[disabled] .md-select-icon, md-select[disabled] .md-select-icon,md-select.md-default-theme[disabled] .md-select-value, md-select[disabled] .md-select-value,md-select.md-default-theme[disabled] .md-select-value.md-select-placeholder, md-select[disabled] .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme .md-select-icon, md-select .md-select-icon{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme md-content md-optgroup, md-select-menu md-content md-optgroup{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme md-content md-option, md-select-menu md-content md-option{color:rgba(0,0,0,0.87)}md-select-menu.md-default-theme md-content md-option[disabled] .md-text, md-select-menu md-content md-option[disabled] .md-text{color:rgba(0,0,0,0.38)}md-select-menu.md-default-theme md-content md-option[selected], md-select-menu md-content md-option[selected]{color:rgb(33,150,243)}md-select-menu.md-default-theme md-content md-option[selected]:focus, md-select-menu md-content md-option[selected]:focus{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme .md-ripple, .md-checkbox-enabled .md-ripple{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme .md-ink-ripple, .md-checkbox-enabled .md-ink-ripple{color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme[selected] .md-ink-ripple, .md-checkbox-enabled[selected] .md-ink-ripple{color:rgba(25,118,210,0.87)}.md-checkbox-enabled.md-default-theme:not(.md-checked) .md-icon, .md-checkbox-enabled:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme[selected] .md-icon, .md-checkbox-enabled[selected] .md-icon{background-color:rgba(25,118,210,0.87)}.md-checkbox-enabled.md-default-theme[selected].md-focused .md-container:before, .md-checkbox-enabled[selected].md-focused .md-container:before{background-color:rgba(25,118,210,0.26)}.md-checkbox-enabled.md-default-theme[selected] .md-icon:after, .md-checkbox-enabled[selected] .md-icon:after{border-color:rgba(255,255,255,0.87)}.md-checkbox-enabled.md-default-theme .md-indeterminate[disabled] .md-container, .md-checkbox-enabled .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}.md-checkbox-enabled.md-default-theme md-option .md-text, .md-checkbox-enabled md-option .md-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-primary .md-focus-ring, md-slider.md-primary .md-focus-ring{background-color:rgba(144,202,249,0.38)}md-slider.md-default-theme.md-primary .md-track.md-track-fill, md-slider.md-primary .md-track.md-track-fill{background-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary .md-thumb:after, md-slider.md-primary .md-thumb:after{border-color:rgb(25,118,210);background-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary .md-sign, md-slider.md-primary .md-sign{background-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary .md-sign:after, md-slider.md-primary .md-sign:after{border-top-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary[md-vertical] .md-sign:after, md-slider.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary .md-thumb-text, md-slider.md-primary .md-thumb-text{color:rgba(255,255,255,0.87)}md-slider.md-default-theme[disabled] .md-thumb:after, md-slider[disabled] .md-thumb:after{border-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:rgba(0,0,0,0.38)}.md-subheader.md-default-theme.md-primary, .md-subheader.md-primary{color:rgb(25,118,210)}md-switch.md-default-theme.md-checked.md-primary .md-ink-ripple, md-switch.md-checked.md-primary .md-ink-ripple{color:rgb(25,118,210)}md-switch.md-default-theme.md-checked.md-primary .md-thumb, md-switch.md-checked.md-primary .md-thumb{background-color:rgb(25,118,210)}md-switch.md-default-theme.md-checked.md-primary .md-bar, md-switch.md-checked.md-primary .md-bar{background-color:rgba(25,118,210,0.5)}md-switch.md-default-theme.md-checked.md-primary.md-focused .md-thumb:before, md-switch.md-checked.md-primary.md-focused .md-thumb:before{background-color:rgba(25,118,210,0.26)}md-tabs.md-default-theme .md-paginator md-icon, md-tabs .md-paginator md-icon{color:rgb(25,118,210)}md-tabs.md-default-theme .md-tab, md-tabs .md-tab{color:rgba(0,0,0,0.54)}md-tabs.md-default-theme .md-tab[disabled], md-tabs .md-tab[disabled],md-tabs.md-default-theme .md-tab[disabled] md-icon, md-tabs .md-tab[disabled] md-icon{color:rgba(0,0,0,0.38)}md-tabs.md-default-theme .md-tab.md-active, md-tabs .md-tab.md-active,md-tabs.md-default-theme .md-tab.md-active md-icon, md-tabs .md-tab.md-active md-icon,md-tabs.md-default-theme .md-tab.md-focused, md-tabs .md-tab.md-focused,md-tabs.md-default-theme .md-tab.md-focused md-icon, md-tabs .md-tab.md-focused md-icon{color:rgb(25,118,210)}md-tabs.md-default-theme .md-tab.md-focused, md-tabs .md-tab.md-focused{background:rgba(25,118,210,0.1)}md-tabs.md-default-theme.md-primary>md-tabs-wrapper, md-tabs.md-primary>md-tabs-wrapper{background-color:rgb(25,118,210)}md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper, md-toolbar>md-tabs>md-tabs-wrapper{background-color:rgb(25,118,210)}md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toast.md-default-theme .md-toast-content .md-button.md-highlight.md-primary, md-toast .md-toast-content .md-button.md-highlight.md-primary{color:rgb(25,118,210)}md-toolbar.md-default-theme:not(.md-menu-toolbar), md-toolbar:not(.md-menu-toolbar){background-color:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar) md-icon, md-toolbar:not(.md-menu-toolbar) md-icon{color:rgba(255,255,255,0.87);fill:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:rgba(255,255,255,0.26);fill:rgba(255,255,255,0.26)} -md-autocomplete.md-default-theme.md-hue-1 input, md-autocomplete.md-hue-1 input{color:rgba(0,0,0,0.87)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-1 li, .md-autocomplete-suggestions-container.md-hue-1 li{color:rgba(0,0,0,0.87)}md-bottom-sheet.md-default-theme.md-hue-1.md-list md-list-item, md-bottom-sheet.md-hue-1.md-list md-list-item{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-primary, .md-button.md-hue-1.md-primary{color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-1.md-primary.md-fab, .md-button.md-hue-1.md-primary.md-fab,.md-button.md-default-theme.md-hue-1.md-primary.md-raised, .md-button.md-hue-1.md-primary.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-1.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-hue-1.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-1.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-hue-1.md-primary.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-hue-1.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-primary.md-fab:not([disabled]):hover, .md-button.md-hue-1.md-primary.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-1.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-hue-1.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-primary.md-raised:not([disabled]):hover, .md-button.md-hue-1.md-primary.md-raised:not([disabled]):hover{background-color:rgb(30,136,229)}.md-button.md-default-theme.md-hue-1.md-primary:not([disabled]) md-icon, .md-button.md-hue-1.md-primary:not([disabled]) md-icon{color:rgb(33,150,243)}._md a.md-default-theme.md-hue-1:not(.md-button).md-primary, ._md a.md-hue-1:not(.md-button).md-primary{color:rgb(33,150,243)}._md a.md-default-theme.md-hue-1:not(.md-button).md-primary:hover, ._md a.md-hue-1:not(.md-button).md-primary:hover{color:rgb(25,118,210)}md-card.md-default-theme.md-hue-1 .md-card-image, md-card.md-hue-1 .md-card-image{border-radius:2px 2px 0 0}md-card.md-default-theme.md-hue-1 md-card-header md-card-header-text .md-subhead, md-card.md-hue-1 md-card-header md-card-header-text .md-subhead,md-card.md-default-theme.md-hue-1 md-card-title md-card-title-text:not(:only-child) .md-subhead, md-card.md-hue-1 md-card-title md-card-title-text:not(:only-child) .md-subhead{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1 .md-ink-ripple, md-checkbox.md-hue-1 .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not(.md-checked) .md-icon, md-checkbox.md-hue-1:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary .md-ripple, md-checkbox.md-hue-1:not([disabled]).md-primary .md-ripple{color:rgb(30,136,229)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-ripple, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary .md-ink-ripple, md-checkbox.md-hue-1:not([disabled]).md-primary .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple{color:rgba(33,150,243,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary:not(.md-checked) .md-icon, md-checkbox.md-hue-1:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-icon, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked .md-icon{background-color:rgba(33,150,243,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked.md-focused .md-container:before, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:rgba(33,150,243,0.26)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-icon:after, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary .md-indeterminate[disabled] .md-container, md-checkbox.md-hue-1:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1[disabled]:not(.md-checked) .md-icon, md-checkbox.md-hue-1[disabled]:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1[disabled] .md-icon:after, md-checkbox.md-hue-1[disabled] .md-icon:after{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1[disabled] .md-label, md-checkbox.md-hue-1[disabled] .md-label{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-1 .md-chips, md-chips.md-hue-1 .md-chips{box-shadow:0 1px rgba(0,0,0,0.12)}md-chips.md-default-theme.md-hue-1 .md-chips.md-focused, md-chips.md-hue-1 .md-chips.md-focused{box-shadow:0 2px rgb(33,150,243)}md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input, md-chips.md-hue-1 .md-chips .md-chip-input-container input{color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input:-moz-placeholder, md-chips.md-hue-1 .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input::-moz-placeholder, md-chips.md-hue-1 .md-chips .md-chip-input-container input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input:-ms-input-placeholder, md-chips.md-hue-1 .md-chips .md-chip-input-container input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input::-webkit-input-placeholder, md-chips.md-hue-1 .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-1 md-chip.md-focused, md-chips.md-hue-1 md-chip.md-focused{background:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-1 md-chip.md-focused md-icon, md-chips.md-hue-1 md-chip.md-focused md-icon{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-1 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator, .md-hue-1 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid rgb(33,150,243)}.md-default-theme.md-hue-1 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled, .md-hue-1 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:rgba(33,150,243,0.6)}.md-default-theme.md-hue-1 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-1 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-default-theme.md-hue-1 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-1 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:rgb(33,150,243);color:rgba(0,0,0,0.87);border-color:transparent}.md-default-theme.md-hue-1 .md-calendar-date-disabled, .md-hue-1 .md-calendar-date-disabled,.md-default-theme.md-hue-1 .md-calendar-month-label-disabled, .md-hue-1 .md-calendar-month-label-disabled{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-1 .md-calendar-month-label md-icon, .md-hue-1 .md-calendar-month-label md-icon,.md-default-theme.md-hue-1 .md-datepicker-input, .md-hue-1 .md-datepicker-input{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-1 .md-datepicker-input:-moz-placeholder, .md-hue-1 .md-datepicker-input:-moz-placeholder,.md-default-theme.md-hue-1 .md-datepicker-input::-moz-placeholder, .md-hue-1 .md-datepicker-input::-moz-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-1 .md-datepicker-input:-ms-input-placeholder, .md-hue-1 .md-datepicker-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-1 .md-datepicker-input::-webkit-input-placeholder, .md-hue-1 .md-datepicker-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-1 .md-datepicker-input-container, .md-hue-1 .md-datepicker-input-container{border-bottom-color:rgba(0,0,0,0.12)}.md-default-theme.md-hue-1 .md-datepicker-input-container.md-datepicker-focused, .md-hue-1 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(33,150,243)}.md-default-theme.md-hue-1 .md-datepicker-triangle-button .md-datepicker-expand-triangle, .md-hue-1 .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:rgba(0,0,0,0.54)}.md-default-theme.md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon, .md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(33,150,243)}md-dialog.md-default-theme.md-hue-1.md-content-overflow .md-actions, md-dialog.md-hue-1.md-content-overflow .md-actions,md-dialog.md-default-theme.md-hue-1.md-content-overflow md-dialog-actions, md-dialog.md-hue-1.md-content-overflow md-dialog-actions,md-divider.md-default-theme.md-hue-1, md-divider.md-hue-1{border-top-color:rgba(0,0,0,0.12)}.layout-gt-lg-row>md-divider.md-default-theme.md-hue-1, .layout-gt-lg-row>md-divider.md-hue-1,.layout-gt-md-row>md-divider.md-default-theme.md-hue-1, .layout-gt-md-row>md-divider.md-hue-1,.layout-gt-sm-row>md-divider.md-default-theme.md-hue-1, .layout-gt-sm-row>md-divider.md-hue-1,.layout-gt-xs-row>md-divider.md-default-theme.md-hue-1, .layout-gt-xs-row>md-divider.md-hue-1,.layout-lg-row>md-divider.md-default-theme.md-hue-1, .layout-lg-row>md-divider.md-hue-1,.layout-md-row>md-divider.md-default-theme.md-hue-1, .layout-md-row>md-divider.md-hue-1,.layout-row>md-divider.md-default-theme.md-hue-1, .layout-row>md-divider.md-hue-1,.layout-sm-row>md-divider.md-default-theme.md-hue-1, .layout-sm-row>md-divider.md-hue-1,.layout-xl-row>md-divider.md-default-theme.md-hue-1, .layout-xl-row>md-divider.md-hue-1,.layout-xs-row>md-divider.md-default-theme.md-hue-1, .layout-xs-row>md-divider.md-hue-1{border-right-color:rgba(0,0,0,0.12)}md-icon.md-default-theme.md-hue-1, md-icon.md-hue-1{color:rgba(0,0,0,0.54)}md-icon.md-default-theme.md-hue-1.md-primary, md-icon.md-hue-1.md-primary{color:rgb(33,150,243)}md-input-container.md-default-theme.md-hue-1 .md-input, md-input-container.md-hue-1 .md-input{color:rgba(0,0,0,0.87);border-color:rgba(0,0,0,0.12)}md-input-container.md-default-theme.md-hue-1 .md-input:-moz-placeholder, md-input-container.md-hue-1 .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-1 .md-input::-moz-placeholder, md-input-container.md-hue-1 .md-input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-1 .md-input:-ms-input-placeholder, md-input-container.md-hue-1 .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-1 .md-input::-webkit-input-placeholder, md-input-container.md-hue-1 .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-1>md-icon, md-input-container.md-hue-1>md-icon{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-1 .md-placeholder, md-input-container.md-hue-1 .md-placeholder,md-input-container.md-default-theme.md-hue-1 label, md-input-container.md-hue-1 label{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-1:not(.md-input-focused):not(.md-input-invalid) label.md-required:after, md-input-container.md-hue-1:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1 .md-input-message-animation .md-char-counter, md-input-container.md-hue-1 .md-input-message-animation .md-char-counter,md-input-container.md-default-theme.md-hue-1 .md-input-messages-animation .md-char-counter, md-input-container.md-hue-1 .md-input-messages-animation .md-char-counter{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-1.md-input-focused .md-input:-moz-placeholder, md-input-container.md-hue-1.md-input-focused .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-1.md-input-focused .md-input::-moz-placeholder, md-input-container.md-hue-1.md-input-focused .md-input::-moz-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1.md-input-focused .md-input:-ms-input-placeholder, md-input-container.md-hue-1.md-input-focused .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1.md-input-focused .md-input::-webkit-input-placeholder, md-input-container.md-hue-1.md-input-focused .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-has-value label, md-input-container.md-hue-1:not(.md-input-invalid).md-input-has-value label{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused .md-input, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-resized .md-input, md-input-container.md-hue-1:not(.md-input-invalid).md-input-resized .md-input{border-color:rgb(33,150,243)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused label, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused label,md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused md-icon, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused md-icon{color:rgb(33,150,243)}md-list.md-default-theme.md-hue-1 md-list-item.md-2-line .md-list-item-text h3, md-list.md-hue-1 md-list-item.md-2-line .md-list-item-text h3,md-list.md-default-theme.md-hue-1 md-list-item.md-2-line .md-list-item-text h4, md-list.md-hue-1 md-list-item.md-2-line .md-list-item-text h4,md-list.md-default-theme.md-hue-1 md-list-item.md-3-line .md-list-item-text h3, md-list.md-hue-1 md-list-item.md-3-line .md-list-item-text h3,md-list.md-default-theme.md-hue-1 md-list-item.md-3-line .md-list-item-text h4, md-list.md-hue-1 md-list-item.md-3-line .md-list-item-text h4{color:rgba(0,0,0,0.87)}md-list.md-default-theme.md-hue-1 md-list-item.md-2-line .md-list-item-text p, md-list.md-hue-1 md-list-item.md-2-line .md-list-item-text p,md-list.md-default-theme.md-hue-1 md-list-item.md-3-line .md-list-item-text p, md-list.md-hue-1 md-list-item.md-3-line .md-list-item-text p{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-1 md-list-item>md-icon, md-list.md-hue-1 md-list-item>md-icon{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-1 md-list-item>md-icon.md-highlight, md-list.md-hue-1 md-list-item>md-icon.md-highlight{color:rgb(33,150,243)}md-menu-content.md-default-theme.md-hue-1 md-menu-item, md-menu-content.md-hue-1 md-menu-item{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-1 md-menu-item md-icon, md-menu-content.md-hue-1 md-menu-item md-icon{color:rgba(0,0,0,0.54)}md-menu-content.md-default-theme.md-hue-1 md-menu-item .md-button[disabled], md-menu-content.md-hue-1 md-menu-item .md-button[disabled],md-menu-content.md-default-theme.md-hue-1 md-menu-item .md-button[disabled] md-icon, md-menu-content.md-hue-1 md-menu-item .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}md-menu-bar.md-default-theme.md-hue-1>button.md-button, md-menu-bar.md-hue-1>button.md-button{color:rgba(0,0,0,0.87);border-radius:2px}md-menu-bar.md-default-theme.md-hue-1 md-menu>button, md-menu-bar.md-hue-1 md-menu>button{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-1 .md-menu>.md-button:after, md-menu-content.md-hue-1 .md-menu>.md-button:after{color:rgba(0,0,0,0.54)}md-toolbar.md-default-theme.md-hue-1.md-menu-toolbar md-toolbar-filler, md-toolbar.md-hue-1.md-menu-toolbar md-toolbar-filler{background-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1.md-menu-toolbar md-toolbar-filler md-icon, md-toolbar.md-hue-1.md-menu-toolbar md-toolbar-filler md-icon{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1 .md-button._md-nav-button.md-unselected, md-nav-bar.md-hue-1 .md-button._md-nav-button.md-unselected{color:rgba(0,0,0,0.54)}md-nav-bar.md-default-theme.md-hue-1 .md-button._md-nav-button[disabled], md-nav-bar.md-hue-1 .md-button._md-nav-button[disabled]{color:rgba(0,0,0,0.38)}md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar, md-nav-bar.md-hue-1.md-primary>.md-nav-bar{background-color:rgb(33,150,243)}md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar{background-color:rgb(33,150,243)}md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-progress-circular.md-default-theme.md-hue-1 path, md-progress-circular.md-hue-1 path{stroke:rgb(33,150,243)}md-progress-linear.md-default-theme.md-hue-1 .md-container, md-progress-linear.md-hue-1 .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-1 .md-bar, md-progress-linear.md-hue-1 .md-bar{background-color:rgb(33,150,243)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-primary .md-bar1, md-progress-linear.md-hue-1[md-mode=buffer].md-primary .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-primary .md-dashed:before, md-progress-linear.md-hue-1[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-1 .md-off, md-radio-button.md-hue-1 .md-off{border-color:rgba(0,0,0,0.54)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary .md-on, md-radio-button.md-hue-1:not([disabled]).md-primary .md-on,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-on, md-radio-button.md-hue-1:not([disabled]) .md-primary .md-on,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary .md-on, md-radio-group.md-hue-1:not([disabled]).md-primary .md-on,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-on, md-radio-group.md-hue-1:not([disabled]) .md-primary .md-on{background-color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-hue-1:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-hue-1:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-hue-1:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-hue-1:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-off, md-radio-group.md-hue-1:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-hue-1:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-hue-1:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-hue-1:not([disabled]) .md-primary .md-checked .md-off{border-color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-hue-1:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-hue-1:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary .md-container .md-ripple, md-radio-group.md-hue-1:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-hue-1:not([disabled]) .md-primary .md-container .md-ripple{color:rgb(30,136,229)}md-radio-button.md-default-theme.md-hue-1[disabled], md-radio-button.md-hue-1[disabled],md-radio-group.md-default-theme.md-hue-1[disabled], md-radio-group.md-hue-1[disabled]{color:rgba(0,0,0,0.38)}md-radio-button.md-default-theme.md-hue-1[disabled] .md-container .md-off, md-radio-button.md-hue-1[disabled] .md-container .md-off,md-radio-button.md-default-theme.md-hue-1[disabled] .md-container .md-on, md-radio-button.md-hue-1[disabled] .md-container .md-on,md-radio-group.md-default-theme.md-hue-1[disabled] .md-container .md-off, md-radio-group.md-hue-1[disabled] .md-container .md-off,md-radio-group.md-default-theme.md-hue-1[disabled] .md-container .md-on, md-radio-group.md-hue-1[disabled] .md-container .md-on{border-color:rgba(0,0,0,0.38)}md-radio-group.md-default-theme.md-hue-1 .md-checked:not([disabled]).md-primary .md-ink-ripple, md-radio-group.md-hue-1 .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-hue-1.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:rgba(33,150,243,0.26)}md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty) .md-checked.md-primary .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty).md-primary .md-checked .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:rgba(33,150,243,0.26)}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-default-theme.md-hue-1 .md-select-value span:first-child:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-hue-1 .md-select-value span:first-child:after{color:rgba(0,0,0,0.38)}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-1 .md-select-value, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-1 .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-1 .md-select-value.md-select-placeholder, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-1 .md-select-value.md-select-placeholder{color:rgb(33,150,243)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-1.md-no-underline .md-select-value, md-input-container.md-input-invalid md-select.md-hue-1.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-1 .md-select-value, md-select.md-hue-1 .md-select-value{border-bottom-color:rgba(0,0,0,0.12)}md-select.md-default-theme.md-hue-1 .md-select-value.md-select-placeholder, md-select.md-hue-1 .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-1.md-no-underline .md-select-value, md-select.md-hue-1.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-1.ng-invalid.ng-touched.md-no-underline .md-select-value, md-select.md-hue-1.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-1:not([disabled]):focus .md-select-value, md-select.md-hue-1:not([disabled]):focus .md-select-value{border-bottom-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-1:not([disabled]):focus .md-select-value.md-select-placeholder, md-select.md-hue-1:not([disabled]):focus .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-1:not([disabled]):focus.md-no-underline .md-select-value, md-select.md-hue-1:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-1[disabled] .md-select-icon, md-select.md-hue-1[disabled] .md-select-icon,md-select.md-default-theme.md-hue-1[disabled] .md-select-value, md-select.md-hue-1[disabled] .md-select-value,md-select.md-default-theme.md-hue-1[disabled] .md-select-value.md-select-placeholder, md-select.md-hue-1[disabled] .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-1 .md-select-icon, md-select.md-hue-1 .md-select-icon{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-1 md-content md-optgroup, md-select-menu.md-hue-1 md-content md-optgroup{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-1 md-content md-option, md-select-menu.md-hue-1 md-content md-option{color:rgba(0,0,0,0.87)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[disabled] .md-text, md-select-menu.md-hue-1 md-content md-option[disabled] .md-text{color:rgba(0,0,0,0.38)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[selected], md-select-menu.md-hue-1 md-content md-option[selected]{color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[selected]:focus, md-select-menu.md-hue-1 md-content md-option[selected]:focus{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-1 .md-ripple, .md-checkbox-enabled.md-hue-1 .md-ripple{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-1 .md-ink-ripple, .md-checkbox-enabled.md-hue-1 .md-ink-ripple{color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected] .md-ink-ripple, .md-checkbox-enabled.md-hue-1[selected] .md-ink-ripple{color:rgba(33,150,243,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-1:not(.md-checked) .md-icon, .md-checkbox-enabled.md-hue-1:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected] .md-icon, .md-checkbox-enabled.md-hue-1[selected] .md-icon{background-color:rgba(33,150,243,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected].md-focused .md-container:before, .md-checkbox-enabled.md-hue-1[selected].md-focused .md-container:before{background-color:rgba(33,150,243,0.26)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected] .md-icon:after, .md-checkbox-enabled.md-hue-1[selected] .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-1 .md-indeterminate[disabled] .md-container, .md-checkbox-enabled.md-hue-1 .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}.md-checkbox-enabled.md-default-theme.md-hue-1 md-option .md-text, .md-checkbox-enabled.md-hue-1 md-option .md-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-1.md-primary .md-focus-ring, md-slider.md-hue-1.md-primary .md-focus-ring{background-color:rgba(144,202,249,0.38)}md-slider.md-default-theme.md-hue-1.md-primary .md-track.md-track-fill, md-slider.md-hue-1.md-primary .md-track.md-track-fill{background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary .md-thumb:after, md-slider.md-hue-1.md-primary .md-thumb:after{border-color:rgb(33,150,243);background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary .md-sign, md-slider.md-hue-1.md-primary .md-sign{background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary .md-sign:after, md-slider.md-hue-1.md-primary .md-sign:after{border-top-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary[md-vertical] .md-sign:after, md-slider.md-hue-1.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary .md-thumb-text, md-slider.md-hue-1.md-primary .md-thumb-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-1[disabled] .md-thumb:after, md-slider.md-hue-1[disabled] .md-thumb:after{border-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:rgba(0,0,0,0.38)}.md-subheader.md-default-theme.md-hue-1.md-primary, .md-subheader.md-hue-1.md-primary{color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-1.md-checked.md-primary .md-ink-ripple, md-switch.md-hue-1.md-checked.md-primary .md-ink-ripple{color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-1.md-checked.md-primary .md-thumb, md-switch.md-hue-1.md-checked.md-primary .md-thumb{background-color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-1.md-checked.md-primary .md-bar, md-switch.md-hue-1.md-checked.md-primary .md-bar{background-color:rgba(33,150,243,0.5)}md-switch.md-default-theme.md-hue-1.md-checked.md-primary.md-focused .md-thumb:before, md-switch.md-hue-1.md-checked.md-primary.md-focused .md-thumb:before{background-color:rgba(33,150,243,0.26)}md-tabs.md-default-theme.md-hue-1 .md-paginator md-icon, md-tabs.md-hue-1 .md-paginator md-icon{color:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-1 .md-tab, md-tabs.md-hue-1 .md-tab{color:rgba(0,0,0,0.54)}md-tabs.md-default-theme.md-hue-1 .md-tab[disabled], md-tabs.md-hue-1 .md-tab[disabled],md-tabs.md-default-theme.md-hue-1 .md-tab[disabled] md-icon, md-tabs.md-hue-1 .md-tab[disabled] md-icon{color:rgba(0,0,0,0.38)}md-tabs.md-default-theme.md-hue-1 .md-tab.md-active, md-tabs.md-hue-1 .md-tab.md-active,md-tabs.md-default-theme.md-hue-1 .md-tab.md-active md-icon, md-tabs.md-hue-1 .md-tab.md-active md-icon,md-tabs.md-default-theme.md-hue-1 .md-tab.md-focused, md-tabs.md-hue-1 .md-tab.md-focused,md-tabs.md-default-theme.md-hue-1 .md-tab.md-focused md-icon, md-tabs.md-hue-1 .md-tab.md-focused md-icon{color:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-1 .md-tab.md-focused, md-tabs.md-hue-1 .md-tab.md-focused{background:rgba(33,150,243,0.1)}md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper, md-tabs.md-hue-1.md-primary>md-tabs-wrapper{background-color:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper{background-color:rgb(33,150,243)}md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toast.md-default-theme.md-hue-1 .md-toast-content .md-button.md-highlight.md-primary, md-toast.md-hue-1 .md-toast-content .md-button.md-highlight.md-primary{color:rgb(33,150,243)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar), md-toolbar.md-hue-1:not(.md-menu-toolbar){background-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar) md-icon, md-toolbar.md-hue-1:not(.md-menu-toolbar) md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar.md-hue-1:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -md-autocomplete.md-default-theme.md-hue-2 input, md-autocomplete.md-hue-2 input{color:rgba(0,0,0,0.87)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-2 li, .md-autocomplete-suggestions-container.md-hue-2 li{color:rgba(0,0,0,0.87)}md-bottom-sheet.md-default-theme.md-hue-2.md-list md-list-item, md-bottom-sheet.md-hue-2.md-list md-list-item{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-primary, .md-button.md-hue-2.md-primary{color:rgb(25,118,210)}.md-button.md-default-theme.md-hue-2.md-primary.md-fab, .md-button.md-hue-2.md-primary.md-fab,.md-button.md-default-theme.md-hue-2.md-primary.md-raised, .md-button.md-hue-2.md-primary.md-raised{color:rgba(255,255,255,0.87);background-color:rgb(25,118,210)}.md-button.md-default-theme.md-hue-2.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-hue-2.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-2.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-hue-2.md-primary.md-raised:not([disabled]) md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-hue-2.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-hue-2.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-primary.md-fab:not([disabled]):hover, .md-button.md-hue-2.md-primary.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-2.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-hue-2.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-primary.md-raised:not([disabled]):hover, .md-button.md-hue-2.md-primary.md-raised:not([disabled]):hover{background-color:rgb(30,136,229)}.md-button.md-default-theme.md-hue-2.md-primary:not([disabled]) md-icon, .md-button.md-hue-2.md-primary:not([disabled]) md-icon{color:rgb(25,118,210)}._md a.md-default-theme.md-hue-2:not(.md-button).md-primary, ._md a.md-hue-2:not(.md-button).md-primary{color:rgb(25,118,210)}._md a.md-default-theme.md-hue-2:not(.md-button).md-primary:hover, ._md a.md-hue-2:not(.md-button).md-primary:hover{color:rgb(25,118,210)}md-card.md-default-theme.md-hue-2 .md-card-image, md-card.md-hue-2 .md-card-image{border-radius:2px 2px 0 0}md-card.md-default-theme.md-hue-2 md-card-header md-card-header-text .md-subhead, md-card.md-hue-2 md-card-header md-card-header-text .md-subhead,md-card.md-default-theme.md-hue-2 md-card-title md-card-title-text:not(:only-child) .md-subhead, md-card.md-hue-2 md-card-title md-card-title-text:not(:only-child) .md-subhead{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2 .md-ink-ripple, md-checkbox.md-hue-2 .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not(.md-checked) .md-icon, md-checkbox.md-hue-2:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary .md-ripple, md-checkbox.md-hue-2:not([disabled]).md-primary .md-ripple{color:rgb(30,136,229)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-ripple, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary .md-ink-ripple, md-checkbox.md-hue-2:not([disabled]).md-primary .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple{color:rgba(25,118,210,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary:not(.md-checked) .md-icon, md-checkbox.md-hue-2:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-icon, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked .md-icon{background-color:rgba(25,118,210,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked.md-focused .md-container:before, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:rgba(25,118,210,0.26)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-icon:after, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked .md-icon:after{border-color:rgba(255,255,255,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary .md-indeterminate[disabled] .md-container, md-checkbox.md-hue-2:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2[disabled]:not(.md-checked) .md-icon, md-checkbox.md-hue-2[disabled]:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2[disabled] .md-icon:after, md-checkbox.md-hue-2[disabled] .md-icon:after{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2[disabled] .md-label, md-checkbox.md-hue-2[disabled] .md-label{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-2 .md-chips, md-chips.md-hue-2 .md-chips{box-shadow:0 1px rgba(0,0,0,0.12)}md-chips.md-default-theme.md-hue-2 .md-chips.md-focused, md-chips.md-hue-2 .md-chips.md-focused{box-shadow:0 2px rgb(25,118,210)}md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input, md-chips.md-hue-2 .md-chips .md-chip-input-container input{color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input:-moz-placeholder, md-chips.md-hue-2 .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input::-moz-placeholder, md-chips.md-hue-2 .md-chips .md-chip-input-container input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input:-ms-input-placeholder, md-chips.md-hue-2 .md-chips .md-chip-input-container input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input::-webkit-input-placeholder, md-chips.md-hue-2 .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-2 md-chip.md-focused, md-chips.md-hue-2 md-chip.md-focused{background:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-chips.md-default-theme.md-hue-2 md-chip.md-focused md-icon, md-chips.md-hue-2 md-chip.md-focused md-icon{color:rgba(255,255,255,0.87)}.md-default-theme.md-hue-2 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator, .md-hue-2 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid rgb(33,150,243)}.md-default-theme.md-hue-2 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled, .md-hue-2 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:rgba(33,150,243,0.6)}.md-default-theme.md-hue-2 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-2 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-default-theme.md-hue-2 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-2 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:rgb(33,150,243);color:rgba(0,0,0,0.87);border-color:transparent}.md-default-theme.md-hue-2 .md-calendar-date-disabled, .md-hue-2 .md-calendar-date-disabled,.md-default-theme.md-hue-2 .md-calendar-month-label-disabled, .md-hue-2 .md-calendar-month-label-disabled{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-2 .md-calendar-month-label md-icon, .md-hue-2 .md-calendar-month-label md-icon,.md-default-theme.md-hue-2 .md-datepicker-input, .md-hue-2 .md-datepicker-input{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-2 .md-datepicker-input:-moz-placeholder, .md-hue-2 .md-datepicker-input:-moz-placeholder,.md-default-theme.md-hue-2 .md-datepicker-input::-moz-placeholder, .md-hue-2 .md-datepicker-input::-moz-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-2 .md-datepicker-input:-ms-input-placeholder, .md-hue-2 .md-datepicker-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-2 .md-datepicker-input::-webkit-input-placeholder, .md-hue-2 .md-datepicker-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-2 .md-datepicker-input-container, .md-hue-2 .md-datepicker-input-container{border-bottom-color:rgba(0,0,0,0.12)}.md-default-theme.md-hue-2 .md-datepicker-input-container.md-datepicker-focused, .md-hue-2 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(25,118,210)}.md-default-theme.md-hue-2 .md-datepicker-triangle-button .md-datepicker-expand-triangle, .md-hue-2 .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:rgba(0,0,0,0.54)}.md-default-theme.md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon, .md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(25,118,210)}md-dialog.md-default-theme.md-hue-2.md-content-overflow .md-actions, md-dialog.md-hue-2.md-content-overflow .md-actions,md-dialog.md-default-theme.md-hue-2.md-content-overflow md-dialog-actions, md-dialog.md-hue-2.md-content-overflow md-dialog-actions,md-divider.md-default-theme.md-hue-2, md-divider.md-hue-2{border-top-color:rgba(0,0,0,0.12)}.layout-gt-lg-row>md-divider.md-default-theme.md-hue-2, .layout-gt-lg-row>md-divider.md-hue-2,.layout-gt-md-row>md-divider.md-default-theme.md-hue-2, .layout-gt-md-row>md-divider.md-hue-2,.layout-gt-sm-row>md-divider.md-default-theme.md-hue-2, .layout-gt-sm-row>md-divider.md-hue-2,.layout-gt-xs-row>md-divider.md-default-theme.md-hue-2, .layout-gt-xs-row>md-divider.md-hue-2,.layout-lg-row>md-divider.md-default-theme.md-hue-2, .layout-lg-row>md-divider.md-hue-2,.layout-md-row>md-divider.md-default-theme.md-hue-2, .layout-md-row>md-divider.md-hue-2,.layout-row>md-divider.md-default-theme.md-hue-2, .layout-row>md-divider.md-hue-2,.layout-sm-row>md-divider.md-default-theme.md-hue-2, .layout-sm-row>md-divider.md-hue-2,.layout-xl-row>md-divider.md-default-theme.md-hue-2, .layout-xl-row>md-divider.md-hue-2,.layout-xs-row>md-divider.md-default-theme.md-hue-2, .layout-xs-row>md-divider.md-hue-2{border-right-color:rgba(0,0,0,0.12)}md-icon.md-default-theme.md-hue-2, md-icon.md-hue-2{color:rgba(0,0,0,0.54)}md-icon.md-default-theme.md-hue-2.md-primary, md-icon.md-hue-2.md-primary{color:rgb(25,118,210)}md-input-container.md-default-theme.md-hue-2 .md-input, md-input-container.md-hue-2 .md-input{color:rgba(0,0,0,0.87);border-color:rgba(0,0,0,0.12)}md-input-container.md-default-theme.md-hue-2 .md-input:-moz-placeholder, md-input-container.md-hue-2 .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-2 .md-input::-moz-placeholder, md-input-container.md-hue-2 .md-input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-2 .md-input:-ms-input-placeholder, md-input-container.md-hue-2 .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-2 .md-input::-webkit-input-placeholder, md-input-container.md-hue-2 .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-2>md-icon, md-input-container.md-hue-2>md-icon{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-2 .md-placeholder, md-input-container.md-hue-2 .md-placeholder,md-input-container.md-default-theme.md-hue-2 label, md-input-container.md-hue-2 label{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-2:not(.md-input-focused):not(.md-input-invalid) label.md-required:after, md-input-container.md-hue-2:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2 .md-input-message-animation .md-char-counter, md-input-container.md-hue-2 .md-input-message-animation .md-char-counter,md-input-container.md-default-theme.md-hue-2 .md-input-messages-animation .md-char-counter, md-input-container.md-hue-2 .md-input-messages-animation .md-char-counter{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-2.md-input-focused .md-input:-moz-placeholder, md-input-container.md-hue-2.md-input-focused .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-2.md-input-focused .md-input::-moz-placeholder, md-input-container.md-hue-2.md-input-focused .md-input::-moz-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2.md-input-focused .md-input:-ms-input-placeholder, md-input-container.md-hue-2.md-input-focused .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2.md-input-focused .md-input::-webkit-input-placeholder, md-input-container.md-hue-2.md-input-focused .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-has-value label, md-input-container.md-hue-2:not(.md-input-invalid).md-input-has-value label{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused .md-input, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-resized .md-input, md-input-container.md-hue-2:not(.md-input-invalid).md-input-resized .md-input{border-color:rgb(25,118,210)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused label, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused label,md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused md-icon, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused md-icon{color:rgb(25,118,210)}md-list.md-default-theme.md-hue-2 md-list-item.md-2-line .md-list-item-text h3, md-list.md-hue-2 md-list-item.md-2-line .md-list-item-text h3,md-list.md-default-theme.md-hue-2 md-list-item.md-2-line .md-list-item-text h4, md-list.md-hue-2 md-list-item.md-2-line .md-list-item-text h4,md-list.md-default-theme.md-hue-2 md-list-item.md-3-line .md-list-item-text h3, md-list.md-hue-2 md-list-item.md-3-line .md-list-item-text h3,md-list.md-default-theme.md-hue-2 md-list-item.md-3-line .md-list-item-text h4, md-list.md-hue-2 md-list-item.md-3-line .md-list-item-text h4{color:rgba(0,0,0,0.87)}md-list.md-default-theme.md-hue-2 md-list-item.md-2-line .md-list-item-text p, md-list.md-hue-2 md-list-item.md-2-line .md-list-item-text p,md-list.md-default-theme.md-hue-2 md-list-item.md-3-line .md-list-item-text p, md-list.md-hue-2 md-list-item.md-3-line .md-list-item-text p{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-2 md-list-item>md-icon, md-list.md-hue-2 md-list-item>md-icon{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-2 md-list-item>md-icon.md-highlight, md-list.md-hue-2 md-list-item>md-icon.md-highlight{color:rgb(25,118,210)}md-menu-content.md-default-theme.md-hue-2 md-menu-item, md-menu-content.md-hue-2 md-menu-item{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-2 md-menu-item md-icon, md-menu-content.md-hue-2 md-menu-item md-icon{color:rgba(0,0,0,0.54)}md-menu-content.md-default-theme.md-hue-2 md-menu-item .md-button[disabled], md-menu-content.md-hue-2 md-menu-item .md-button[disabled],md-menu-content.md-default-theme.md-hue-2 md-menu-item .md-button[disabled] md-icon, md-menu-content.md-hue-2 md-menu-item .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}md-menu-bar.md-default-theme.md-hue-2>button.md-button, md-menu-bar.md-hue-2>button.md-button{color:rgba(0,0,0,0.87);border-radius:2px}md-menu-bar.md-default-theme.md-hue-2 md-menu>button, md-menu-bar.md-hue-2 md-menu>button{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-2 .md-menu>.md-button:after, md-menu-content.md-hue-2 .md-menu>.md-button:after{color:rgba(0,0,0,0.54)}md-toolbar.md-default-theme.md-hue-2.md-menu-toolbar md-toolbar-filler, md-toolbar.md-hue-2.md-menu-toolbar md-toolbar-filler{background-color:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme.md-hue-2.md-menu-toolbar md-toolbar-filler md-icon, md-toolbar.md-hue-2.md-menu-toolbar md-toolbar-filler md-icon{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme.md-hue-2 .md-button._md-nav-button.md-unselected, md-nav-bar.md-hue-2 .md-button._md-nav-button.md-unselected{color:rgba(0,0,0,0.54)}md-nav-bar.md-default-theme.md-hue-2 .md-button._md-nav-button[disabled], md-nav-bar.md-hue-2 .md-button._md-nav-button[disabled]{color:rgba(0,0,0,0.38)}md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar, md-nav-bar.md-hue-2.md-primary>.md-nav-bar{background-color:rgb(25,118,210)}md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar{background-color:rgb(25,118,210)}md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-progress-circular.md-default-theme.md-hue-2 path, md-progress-circular.md-hue-2 path{stroke:rgb(25,118,210)}md-progress-linear.md-default-theme.md-hue-2 .md-container, md-progress-linear.md-hue-2 .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-2 .md-bar, md-progress-linear.md-hue-2 .md-bar{background-color:rgb(25,118,210)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-primary .md-bar1, md-progress-linear.md-hue-2[md-mode=buffer].md-primary .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-primary .md-dashed:before, md-progress-linear.md-hue-2[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-2 .md-off, md-radio-button.md-hue-2 .md-off{border-color:rgba(0,0,0,0.54)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary .md-on, md-radio-button.md-hue-2:not([disabled]).md-primary .md-on,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-on, md-radio-button.md-hue-2:not([disabled]) .md-primary .md-on,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary .md-on, md-radio-group.md-hue-2:not([disabled]).md-primary .md-on,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-on, md-radio-group.md-hue-2:not([disabled]) .md-primary .md-on{background-color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-hue-2:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-hue-2:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-hue-2:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-hue-2:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-off, md-radio-group.md-hue-2:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-hue-2:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-hue-2:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-hue-2:not([disabled]) .md-primary .md-checked .md-off{border-color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-hue-2:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-hue-2:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary .md-container .md-ripple, md-radio-group.md-hue-2:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-hue-2:not([disabled]) .md-primary .md-container .md-ripple{color:rgb(30,136,229)}md-radio-button.md-default-theme.md-hue-2[disabled], md-radio-button.md-hue-2[disabled],md-radio-group.md-default-theme.md-hue-2[disabled], md-radio-group.md-hue-2[disabled]{color:rgba(0,0,0,0.38)}md-radio-button.md-default-theme.md-hue-2[disabled] .md-container .md-off, md-radio-button.md-hue-2[disabled] .md-container .md-off,md-radio-button.md-default-theme.md-hue-2[disabled] .md-container .md-on, md-radio-button.md-hue-2[disabled] .md-container .md-on,md-radio-group.md-default-theme.md-hue-2[disabled] .md-container .md-off, md-radio-group.md-hue-2[disabled] .md-container .md-off,md-radio-group.md-default-theme.md-hue-2[disabled] .md-container .md-on, md-radio-group.md-hue-2[disabled] .md-container .md-on{border-color:rgba(0,0,0,0.38)}md-radio-group.md-default-theme.md-hue-2 .md-checked:not([disabled]).md-primary .md-ink-ripple, md-radio-group.md-hue-2 .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-hue-2.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:rgba(25,118,210,0.26)}md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty) .md-checked.md-primary .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty).md-primary .md-checked .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:rgba(25,118,210,0.26)}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-default-theme.md-hue-2 .md-select-value span:first-child:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-hue-2 .md-select-value span:first-child:after{color:rgba(0,0,0,0.38)}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-2 .md-select-value, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-2 .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-2 .md-select-value.md-select-placeholder, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-2 .md-select-value.md-select-placeholder{color:rgb(25,118,210)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-2.md-no-underline .md-select-value, md-input-container.md-input-invalid md-select.md-hue-2.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-2 .md-select-value, md-select.md-hue-2 .md-select-value{border-bottom-color:rgba(0,0,0,0.12)}md-select.md-default-theme.md-hue-2 .md-select-value.md-select-placeholder, md-select.md-hue-2 .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-2.md-no-underline .md-select-value, md-select.md-hue-2.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-2.ng-invalid.ng-touched.md-no-underline .md-select-value, md-select.md-hue-2.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-2:not([disabled]):focus .md-select-value, md-select.md-hue-2:not([disabled]):focus .md-select-value{border-bottom-color:rgb(25,118,210);color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-2:not([disabled]):focus .md-select-value.md-select-placeholder, md-select.md-hue-2:not([disabled]):focus .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-2:not([disabled]):focus.md-no-underline .md-select-value, md-select.md-hue-2:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-2[disabled] .md-select-icon, md-select.md-hue-2[disabled] .md-select-icon,md-select.md-default-theme.md-hue-2[disabled] .md-select-value, md-select.md-hue-2[disabled] .md-select-value,md-select.md-default-theme.md-hue-2[disabled] .md-select-value.md-select-placeholder, md-select.md-hue-2[disabled] .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-2 .md-select-icon, md-select.md-hue-2 .md-select-icon{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-2 md-content md-optgroup, md-select-menu.md-hue-2 md-content md-optgroup{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-2 md-content md-option, md-select-menu.md-hue-2 md-content md-option{color:rgba(0,0,0,0.87)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[disabled] .md-text, md-select-menu.md-hue-2 md-content md-option[disabled] .md-text{color:rgba(0,0,0,0.38)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[selected], md-select-menu.md-hue-2 md-content md-option[selected]{color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[selected]:focus, md-select-menu.md-hue-2 md-content md-option[selected]:focus{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-2 .md-ripple, .md-checkbox-enabled.md-hue-2 .md-ripple{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-2 .md-ink-ripple, .md-checkbox-enabled.md-hue-2 .md-ink-ripple{color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected] .md-ink-ripple, .md-checkbox-enabled.md-hue-2[selected] .md-ink-ripple{color:rgba(25,118,210,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-2:not(.md-checked) .md-icon, .md-checkbox-enabled.md-hue-2:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected] .md-icon, .md-checkbox-enabled.md-hue-2[selected] .md-icon{background-color:rgba(25,118,210,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected].md-focused .md-container:before, .md-checkbox-enabled.md-hue-2[selected].md-focused .md-container:before{background-color:rgba(25,118,210,0.26)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected] .md-icon:after, .md-checkbox-enabled.md-hue-2[selected] .md-icon:after{border-color:rgba(255,255,255,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-2 .md-indeterminate[disabled] .md-container, .md-checkbox-enabled.md-hue-2 .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}.md-checkbox-enabled.md-default-theme.md-hue-2 md-option .md-text, .md-checkbox-enabled.md-hue-2 md-option .md-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-2.md-primary .md-focus-ring, md-slider.md-hue-2.md-primary .md-focus-ring{background-color:rgba(144,202,249,0.38)}md-slider.md-default-theme.md-hue-2.md-primary .md-track.md-track-fill, md-slider.md-hue-2.md-primary .md-track.md-track-fill{background-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary .md-thumb:after, md-slider.md-hue-2.md-primary .md-thumb:after{border-color:rgb(25,118,210);background-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary .md-sign, md-slider.md-hue-2.md-primary .md-sign{background-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary .md-sign:after, md-slider.md-hue-2.md-primary .md-sign:after{border-top-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary[md-vertical] .md-sign:after, md-slider.md-hue-2.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary .md-thumb-text, md-slider.md-hue-2.md-primary .md-thumb-text{color:rgba(255,255,255,0.87)}md-slider.md-default-theme.md-hue-2[disabled] .md-thumb:after, md-slider.md-hue-2[disabled] .md-thumb:after{border-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:rgba(0,0,0,0.38)}.md-subheader.md-default-theme.md-hue-2.md-primary, .md-subheader.md-hue-2.md-primary{color:rgb(25,118,210)}md-switch.md-default-theme.md-hue-2.md-checked.md-primary .md-ink-ripple, md-switch.md-hue-2.md-checked.md-primary .md-ink-ripple{color:rgb(25,118,210)}md-switch.md-default-theme.md-hue-2.md-checked.md-primary .md-thumb, md-switch.md-hue-2.md-checked.md-primary .md-thumb{background-color:rgb(25,118,210)}md-switch.md-default-theme.md-hue-2.md-checked.md-primary .md-bar, md-switch.md-hue-2.md-checked.md-primary .md-bar{background-color:rgba(25,118,210,0.5)}md-switch.md-default-theme.md-hue-2.md-checked.md-primary.md-focused .md-thumb:before, md-switch.md-hue-2.md-checked.md-primary.md-focused .md-thumb:before{background-color:rgba(25,118,210,0.26)}md-tabs.md-default-theme.md-hue-2 .md-paginator md-icon, md-tabs.md-hue-2 .md-paginator md-icon{color:rgb(25,118,210)}md-tabs.md-default-theme.md-hue-2 .md-tab, md-tabs.md-hue-2 .md-tab{color:rgba(0,0,0,0.54)}md-tabs.md-default-theme.md-hue-2 .md-tab[disabled], md-tabs.md-hue-2 .md-tab[disabled],md-tabs.md-default-theme.md-hue-2 .md-tab[disabled] md-icon, md-tabs.md-hue-2 .md-tab[disabled] md-icon{color:rgba(0,0,0,0.38)}md-tabs.md-default-theme.md-hue-2 .md-tab.md-active, md-tabs.md-hue-2 .md-tab.md-active,md-tabs.md-default-theme.md-hue-2 .md-tab.md-active md-icon, md-tabs.md-hue-2 .md-tab.md-active md-icon,md-tabs.md-default-theme.md-hue-2 .md-tab.md-focused, md-tabs.md-hue-2 .md-tab.md-focused,md-tabs.md-default-theme.md-hue-2 .md-tab.md-focused md-icon, md-tabs.md-hue-2 .md-tab.md-focused md-icon{color:rgb(25,118,210)}md-tabs.md-default-theme.md-hue-2 .md-tab.md-focused, md-tabs.md-hue-2 .md-tab.md-focused{background:rgba(25,118,210,0.1)}md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper, md-tabs.md-hue-2.md-primary>md-tabs-wrapper{background-color:rgb(25,118,210)}md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper{background-color:rgb(25,118,210)}md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toast.md-default-theme.md-hue-2 .md-toast-content .md-button.md-highlight.md-primary, md-toast.md-hue-2 .md-toast-content .md-button.md-highlight.md-primary{color:rgb(25,118,210)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar), md-toolbar.md-hue-2:not(.md-menu-toolbar){background-color:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar) md-icon, md-toolbar.md-hue-2:not(.md-menu-toolbar) md-icon{color:rgba(255,255,255,0.87);fill:rgba(255,255,255,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar.md-hue-2:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:rgba(255,255,255,0.26);fill:rgba(255,255,255,0.26)} -md-autocomplete.md-default-theme.md-hue-3 input, md-autocomplete.md-hue-3 input{color:rgba(0,0,0,0.87)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-3 li, .md-autocomplete-suggestions-container.md-hue-3 li{color:rgba(0,0,0,0.87)}md-bottom-sheet.md-default-theme.md-hue-3.md-list md-list-item, md-bottom-sheet.md-hue-3.md-list md-list-item{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-primary, .md-button.md-hue-3.md-primary{color:rgb(68,138,255)}.md-button.md-default-theme.md-hue-3.md-primary.md-fab, .md-button.md-hue-3.md-primary.md-fab,.md-button.md-default-theme.md-hue-3.md-primary.md-raised, .md-button.md-hue-3.md-primary.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(68,138,255)}.md-button.md-default-theme.md-hue-3.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-hue-3.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-3.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-hue-3.md-primary.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-hue-3.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-primary.md-fab:not([disabled]):hover, .md-button.md-hue-3.md-primary.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-3.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-hue-3.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-primary.md-raised:not([disabled]):hover, .md-button.md-hue-3.md-primary.md-raised:not([disabled]):hover{background-color:rgb(30,136,229)}.md-button.md-default-theme.md-hue-3.md-primary:not([disabled]) md-icon, .md-button.md-hue-3.md-primary:not([disabled]) md-icon{color:rgb(68,138,255)}._md a.md-default-theme.md-hue-3:not(.md-button).md-primary, ._md a.md-hue-3:not(.md-button).md-primary{color:rgb(68,138,255)}._md a.md-default-theme.md-hue-3:not(.md-button).md-primary:hover, ._md a.md-hue-3:not(.md-button).md-primary:hover{color:rgb(25,118,210)}md-card.md-default-theme.md-hue-3 .md-card-image, md-card.md-hue-3 .md-card-image{border-radius:2px 2px 0 0}md-card.md-default-theme.md-hue-3 md-card-header md-card-header-text .md-subhead, md-card.md-hue-3 md-card-header md-card-header-text .md-subhead,md-card.md-default-theme.md-hue-3 md-card-title md-card-title-text:not(:only-child) .md-subhead, md-card.md-hue-3 md-card-title md-card-title-text:not(:only-child) .md-subhead{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3 .md-ink-ripple, md-checkbox.md-hue-3 .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not(.md-checked) .md-icon, md-checkbox.md-hue-3:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary .md-ripple, md-checkbox.md-hue-3:not([disabled]).md-primary .md-ripple{color:rgb(30,136,229)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-ripple, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary .md-ink-ripple, md-checkbox.md-hue-3:not([disabled]).md-primary .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple{color:rgba(68,138,255,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary:not(.md-checked) .md-icon, md-checkbox.md-hue-3:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-icon, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked .md-icon{background-color:rgba(68,138,255,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked.md-focused .md-container:before, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:rgba(68,138,255,0.26)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-icon:after, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary .md-indeterminate[disabled] .md-container, md-checkbox.md-hue-3:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3[disabled]:not(.md-checked) .md-icon, md-checkbox.md-hue-3[disabled]:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3[disabled] .md-icon:after, md-checkbox.md-hue-3[disabled] .md-icon:after{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3[disabled] .md-label, md-checkbox.md-hue-3[disabled] .md-label{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-3 .md-chips, md-chips.md-hue-3 .md-chips{box-shadow:0 1px rgba(0,0,0,0.12)}md-chips.md-default-theme.md-hue-3 .md-chips.md-focused, md-chips.md-hue-3 .md-chips.md-focused{box-shadow:0 2px rgb(68,138,255)}md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input, md-chips.md-hue-3 .md-chips .md-chip-input-container input{color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input:-moz-placeholder, md-chips.md-hue-3 .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input::-moz-placeholder, md-chips.md-hue-3 .md-chips .md-chip-input-container input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input:-ms-input-placeholder, md-chips.md-hue-3 .md-chips .md-chip-input-container input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input::-webkit-input-placeholder, md-chips.md-hue-3 .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-3 md-chip.md-focused, md-chips.md-hue-3 md-chip.md-focused{background:rgb(68,138,255);color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-3 md-chip.md-focused md-icon, md-chips.md-hue-3 md-chip.md-focused md-icon{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-3 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator, .md-hue-3 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid rgb(33,150,243)}.md-default-theme.md-hue-3 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled, .md-hue-3 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:rgba(33,150,243,0.6)}.md-default-theme.md-hue-3 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-3 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-default-theme.md-hue-3 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-3 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:rgb(33,150,243);color:rgba(0,0,0,0.87);border-color:transparent}.md-default-theme.md-hue-3 .md-calendar-date-disabled, .md-hue-3 .md-calendar-date-disabled,.md-default-theme.md-hue-3 .md-calendar-month-label-disabled, .md-hue-3 .md-calendar-month-label-disabled{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-3 .md-calendar-month-label md-icon, .md-hue-3 .md-calendar-month-label md-icon,.md-default-theme.md-hue-3 .md-datepicker-input, .md-hue-3 .md-datepicker-input{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-3 .md-datepicker-input:-moz-placeholder, .md-hue-3 .md-datepicker-input:-moz-placeholder,.md-default-theme.md-hue-3 .md-datepicker-input::-moz-placeholder, .md-hue-3 .md-datepicker-input::-moz-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-3 .md-datepicker-input:-ms-input-placeholder, .md-hue-3 .md-datepicker-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-3 .md-datepicker-input::-webkit-input-placeholder, .md-hue-3 .md-datepicker-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-3 .md-datepicker-input-container, .md-hue-3 .md-datepicker-input-container{border-bottom-color:rgba(0,0,0,0.12)}.md-default-theme.md-hue-3 .md-datepicker-input-container.md-datepicker-focused, .md-hue-3 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(68,138,255)}.md-default-theme.md-hue-3 .md-datepicker-triangle-button .md-datepicker-expand-triangle, .md-hue-3 .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:rgba(0,0,0,0.54)}.md-default-theme.md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon, .md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(68,138,255)}md-dialog.md-default-theme.md-hue-3.md-content-overflow .md-actions, md-dialog.md-hue-3.md-content-overflow .md-actions,md-dialog.md-default-theme.md-hue-3.md-content-overflow md-dialog-actions, md-dialog.md-hue-3.md-content-overflow md-dialog-actions,md-divider.md-default-theme.md-hue-3, md-divider.md-hue-3{border-top-color:rgba(0,0,0,0.12)}.layout-gt-lg-row>md-divider.md-default-theme.md-hue-3, .layout-gt-lg-row>md-divider.md-hue-3,.layout-gt-md-row>md-divider.md-default-theme.md-hue-3, .layout-gt-md-row>md-divider.md-hue-3,.layout-gt-sm-row>md-divider.md-default-theme.md-hue-3, .layout-gt-sm-row>md-divider.md-hue-3,.layout-gt-xs-row>md-divider.md-default-theme.md-hue-3, .layout-gt-xs-row>md-divider.md-hue-3,.layout-lg-row>md-divider.md-default-theme.md-hue-3, .layout-lg-row>md-divider.md-hue-3,.layout-md-row>md-divider.md-default-theme.md-hue-3, .layout-md-row>md-divider.md-hue-3,.layout-row>md-divider.md-default-theme.md-hue-3, .layout-row>md-divider.md-hue-3,.layout-sm-row>md-divider.md-default-theme.md-hue-3, .layout-sm-row>md-divider.md-hue-3,.layout-xl-row>md-divider.md-default-theme.md-hue-3, .layout-xl-row>md-divider.md-hue-3,.layout-xs-row>md-divider.md-default-theme.md-hue-3, .layout-xs-row>md-divider.md-hue-3{border-right-color:rgba(0,0,0,0.12)}md-icon.md-default-theme.md-hue-3, md-icon.md-hue-3{color:rgba(0,0,0,0.54)}md-icon.md-default-theme.md-hue-3.md-primary, md-icon.md-hue-3.md-primary{color:rgb(68,138,255)}md-input-container.md-default-theme.md-hue-3 .md-input, md-input-container.md-hue-3 .md-input{color:rgba(0,0,0,0.87);border-color:rgba(0,0,0,0.12)}md-input-container.md-default-theme.md-hue-3 .md-input:-moz-placeholder, md-input-container.md-hue-3 .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-3 .md-input::-moz-placeholder, md-input-container.md-hue-3 .md-input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-3 .md-input:-ms-input-placeholder, md-input-container.md-hue-3 .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-3 .md-input::-webkit-input-placeholder, md-input-container.md-hue-3 .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-3>md-icon, md-input-container.md-hue-3>md-icon{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-3 .md-placeholder, md-input-container.md-hue-3 .md-placeholder,md-input-container.md-default-theme.md-hue-3 label, md-input-container.md-hue-3 label{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-3:not(.md-input-focused):not(.md-input-invalid) label.md-required:after, md-input-container.md-hue-3:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3 .md-input-message-animation .md-char-counter, md-input-container.md-hue-3 .md-input-message-animation .md-char-counter,md-input-container.md-default-theme.md-hue-3 .md-input-messages-animation .md-char-counter, md-input-container.md-hue-3 .md-input-messages-animation .md-char-counter{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-3.md-input-focused .md-input:-moz-placeholder, md-input-container.md-hue-3.md-input-focused .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-3.md-input-focused .md-input::-moz-placeholder, md-input-container.md-hue-3.md-input-focused .md-input::-moz-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3.md-input-focused .md-input:-ms-input-placeholder, md-input-container.md-hue-3.md-input-focused .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3.md-input-focused .md-input::-webkit-input-placeholder, md-input-container.md-hue-3.md-input-focused .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-has-value label, md-input-container.md-hue-3:not(.md-input-invalid).md-input-has-value label{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused .md-input, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-resized .md-input, md-input-container.md-hue-3:not(.md-input-invalid).md-input-resized .md-input{border-color:rgb(68,138,255)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused label, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused label,md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused md-icon, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused md-icon{color:rgb(68,138,255)}md-list.md-default-theme.md-hue-3 md-list-item.md-2-line .md-list-item-text h3, md-list.md-hue-3 md-list-item.md-2-line .md-list-item-text h3,md-list.md-default-theme.md-hue-3 md-list-item.md-2-line .md-list-item-text h4, md-list.md-hue-3 md-list-item.md-2-line .md-list-item-text h4,md-list.md-default-theme.md-hue-3 md-list-item.md-3-line .md-list-item-text h3, md-list.md-hue-3 md-list-item.md-3-line .md-list-item-text h3,md-list.md-default-theme.md-hue-3 md-list-item.md-3-line .md-list-item-text h4, md-list.md-hue-3 md-list-item.md-3-line .md-list-item-text h4{color:rgba(0,0,0,0.87)}md-list.md-default-theme.md-hue-3 md-list-item.md-2-line .md-list-item-text p, md-list.md-hue-3 md-list-item.md-2-line .md-list-item-text p,md-list.md-default-theme.md-hue-3 md-list-item.md-3-line .md-list-item-text p, md-list.md-hue-3 md-list-item.md-3-line .md-list-item-text p{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-3 md-list-item>md-icon, md-list.md-hue-3 md-list-item>md-icon{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-3 md-list-item>md-icon.md-highlight, md-list.md-hue-3 md-list-item>md-icon.md-highlight{color:rgb(68,138,255)}md-menu-content.md-default-theme.md-hue-3 md-menu-item, md-menu-content.md-hue-3 md-menu-item{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-3 md-menu-item md-icon, md-menu-content.md-hue-3 md-menu-item md-icon{color:rgba(0,0,0,0.54)}md-menu-content.md-default-theme.md-hue-3 md-menu-item .md-button[disabled], md-menu-content.md-hue-3 md-menu-item .md-button[disabled],md-menu-content.md-default-theme.md-hue-3 md-menu-item .md-button[disabled] md-icon, md-menu-content.md-hue-3 md-menu-item .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}md-menu-bar.md-default-theme.md-hue-3>button.md-button, md-menu-bar.md-hue-3>button.md-button{color:rgba(0,0,0,0.87);border-radius:2px}md-menu-bar.md-default-theme.md-hue-3 md-menu>button, md-menu-bar.md-hue-3 md-menu>button{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-3 .md-menu>.md-button:after, md-menu-content.md-hue-3 .md-menu>.md-button:after{color:rgba(0,0,0,0.54)}md-toolbar.md-default-theme.md-hue-3.md-menu-toolbar md-toolbar-filler, md-toolbar.md-hue-3.md-menu-toolbar md-toolbar-filler{background-color:rgb(68,138,255);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3.md-menu-toolbar md-toolbar-filler md-icon, md-toolbar.md-hue-3.md-menu-toolbar md-toolbar-filler md-icon{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3 .md-button._md-nav-button.md-unselected, md-nav-bar.md-hue-3 .md-button._md-nav-button.md-unselected{color:rgba(0,0,0,0.54)}md-nav-bar.md-default-theme.md-hue-3 .md-button._md-nav-button[disabled], md-nav-bar.md-hue-3 .md-button._md-nav-button[disabled]{color:rgba(0,0,0,0.38)}md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar, md-nav-bar.md-hue-3.md-primary>.md-nav-bar{background-color:rgb(68,138,255)}md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar{background-color:rgb(68,138,255)}md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-progress-circular.md-default-theme.md-hue-3 path, md-progress-circular.md-hue-3 path{stroke:rgb(68,138,255)}md-progress-linear.md-default-theme.md-hue-3 .md-container, md-progress-linear.md-hue-3 .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-3 .md-bar, md-progress-linear.md-hue-3 .md-bar{background-color:rgb(68,138,255)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-primary .md-bar1, md-progress-linear.md-hue-3[md-mode=buffer].md-primary .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-primary .md-dashed:before, md-progress-linear.md-hue-3[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-3 .md-off, md-radio-button.md-hue-3 .md-off{border-color:rgba(0,0,0,0.54)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary .md-on, md-radio-button.md-hue-3:not([disabled]).md-primary .md-on,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-on, md-radio-button.md-hue-3:not([disabled]) .md-primary .md-on,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary .md-on, md-radio-group.md-hue-3:not([disabled]).md-primary .md-on,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-on, md-radio-group.md-hue-3:not([disabled]) .md-primary .md-on{background-color:rgba(68,138,255,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-hue-3:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-hue-3:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-hue-3:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-hue-3:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-off, md-radio-group.md-hue-3:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-hue-3:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-hue-3:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-hue-3:not([disabled]) .md-primary .md-checked .md-off{border-color:rgba(68,138,255,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:rgba(68,138,255,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-hue-3:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-hue-3:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary .md-container .md-ripple, md-radio-group.md-hue-3:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-hue-3:not([disabled]) .md-primary .md-container .md-ripple{color:rgb(30,136,229)}md-radio-button.md-default-theme.md-hue-3[disabled], md-radio-button.md-hue-3[disabled],md-radio-group.md-default-theme.md-hue-3[disabled], md-radio-group.md-hue-3[disabled]{color:rgba(0,0,0,0.38)}md-radio-button.md-default-theme.md-hue-3[disabled] .md-container .md-off, md-radio-button.md-hue-3[disabled] .md-container .md-off,md-radio-button.md-default-theme.md-hue-3[disabled] .md-container .md-on, md-radio-button.md-hue-3[disabled] .md-container .md-on,md-radio-group.md-default-theme.md-hue-3[disabled] .md-container .md-off, md-radio-group.md-hue-3[disabled] .md-container .md-off,md-radio-group.md-default-theme.md-hue-3[disabled] .md-container .md-on, md-radio-group.md-hue-3[disabled] .md-container .md-on{border-color:rgba(0,0,0,0.38)}md-radio-group.md-default-theme.md-hue-3 .md-checked:not([disabled]).md-primary .md-ink-ripple, md-radio-group.md-hue-3 .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-hue-3.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:rgba(68,138,255,0.26)}md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty) .md-checked.md-primary .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty).md-primary .md-checked .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:rgba(68,138,255,0.26)}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-default-theme.md-hue-3 .md-select-value span:first-child:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-hue-3 .md-select-value span:first-child:after{color:rgba(0,0,0,0.38)}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-3 .md-select-value, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-3 .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-3 .md-select-value.md-select-placeholder, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-3 .md-select-value.md-select-placeholder{color:rgb(68,138,255)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-3.md-no-underline .md-select-value, md-input-container.md-input-invalid md-select.md-hue-3.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-3 .md-select-value, md-select.md-hue-3 .md-select-value{border-bottom-color:rgba(0,0,0,0.12)}md-select.md-default-theme.md-hue-3 .md-select-value.md-select-placeholder, md-select.md-hue-3 .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-3.md-no-underline .md-select-value, md-select.md-hue-3.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-3.ng-invalid.ng-touched.md-no-underline .md-select-value, md-select.md-hue-3.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-3:not([disabled]):focus .md-select-value, md-select.md-hue-3:not([disabled]):focus .md-select-value{border-bottom-color:rgb(68,138,255);color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-3:not([disabled]):focus .md-select-value.md-select-placeholder, md-select.md-hue-3:not([disabled]):focus .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-3:not([disabled]):focus.md-no-underline .md-select-value, md-select.md-hue-3:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-3[disabled] .md-select-icon, md-select.md-hue-3[disabled] .md-select-icon,md-select.md-default-theme.md-hue-3[disabled] .md-select-value, md-select.md-hue-3[disabled] .md-select-value,md-select.md-default-theme.md-hue-3[disabled] .md-select-value.md-select-placeholder, md-select.md-hue-3[disabled] .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-3 .md-select-icon, md-select.md-hue-3 .md-select-icon{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-3 md-content md-optgroup, md-select-menu.md-hue-3 md-content md-optgroup{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-3 md-content md-option, md-select-menu.md-hue-3 md-content md-option{color:rgba(0,0,0,0.87)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[disabled] .md-text, md-select-menu.md-hue-3 md-content md-option[disabled] .md-text{color:rgba(0,0,0,0.38)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[selected], md-select-menu.md-hue-3 md-content md-option[selected]{color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[selected]:focus, md-select-menu.md-hue-3 md-content md-option[selected]:focus{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-3 .md-ripple, .md-checkbox-enabled.md-hue-3 .md-ripple{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-3 .md-ink-ripple, .md-checkbox-enabled.md-hue-3 .md-ink-ripple{color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected] .md-ink-ripple, .md-checkbox-enabled.md-hue-3[selected] .md-ink-ripple{color:rgba(68,138,255,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-3:not(.md-checked) .md-icon, .md-checkbox-enabled.md-hue-3:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected] .md-icon, .md-checkbox-enabled.md-hue-3[selected] .md-icon{background-color:rgba(68,138,255,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected].md-focused .md-container:before, .md-checkbox-enabled.md-hue-3[selected].md-focused .md-container:before{background-color:rgba(68,138,255,0.26)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected] .md-icon:after, .md-checkbox-enabled.md-hue-3[selected] .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-3 .md-indeterminate[disabled] .md-container, .md-checkbox-enabled.md-hue-3 .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}.md-checkbox-enabled.md-default-theme.md-hue-3 md-option .md-text, .md-checkbox-enabled.md-hue-3 md-option .md-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-3.md-primary .md-focus-ring, md-slider.md-hue-3.md-primary .md-focus-ring{background-color:rgba(144,202,249,0.38)}md-slider.md-default-theme.md-hue-3.md-primary .md-track.md-track-fill, md-slider.md-hue-3.md-primary .md-track.md-track-fill{background-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary .md-thumb:after, md-slider.md-hue-3.md-primary .md-thumb:after{border-color:rgb(68,138,255);background-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary .md-sign, md-slider.md-hue-3.md-primary .md-sign{background-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary .md-sign:after, md-slider.md-hue-3.md-primary .md-sign:after{border-top-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary[md-vertical] .md-sign:after, md-slider.md-hue-3.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary .md-thumb-text, md-slider.md-hue-3.md-primary .md-thumb-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-3[disabled] .md-thumb:after, md-slider.md-hue-3[disabled] .md-thumb:after{border-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:rgba(0,0,0,0.38)}.md-subheader.md-default-theme.md-hue-3.md-primary, .md-subheader.md-hue-3.md-primary{color:rgb(68,138,255)}md-switch.md-default-theme.md-hue-3.md-checked.md-primary .md-ink-ripple, md-switch.md-hue-3.md-checked.md-primary .md-ink-ripple{color:rgb(68,138,255)}md-switch.md-default-theme.md-hue-3.md-checked.md-primary .md-thumb, md-switch.md-hue-3.md-checked.md-primary .md-thumb{background-color:rgb(68,138,255)}md-switch.md-default-theme.md-hue-3.md-checked.md-primary .md-bar, md-switch.md-hue-3.md-checked.md-primary .md-bar{background-color:rgba(68,138,255,0.5)}md-switch.md-default-theme.md-hue-3.md-checked.md-primary.md-focused .md-thumb:before, md-switch.md-hue-3.md-checked.md-primary.md-focused .md-thumb:before{background-color:rgba(68,138,255,0.26)}md-tabs.md-default-theme.md-hue-3 .md-paginator md-icon, md-tabs.md-hue-3 .md-paginator md-icon{color:rgb(68,138,255)}md-tabs.md-default-theme.md-hue-3 .md-tab, md-tabs.md-hue-3 .md-tab{color:rgba(0,0,0,0.54)}md-tabs.md-default-theme.md-hue-3 .md-tab[disabled], md-tabs.md-hue-3 .md-tab[disabled],md-tabs.md-default-theme.md-hue-3 .md-tab[disabled] md-icon, md-tabs.md-hue-3 .md-tab[disabled] md-icon{color:rgba(0,0,0,0.38)}md-tabs.md-default-theme.md-hue-3 .md-tab.md-active, md-tabs.md-hue-3 .md-tab.md-active,md-tabs.md-default-theme.md-hue-3 .md-tab.md-active md-icon, md-tabs.md-hue-3 .md-tab.md-active md-icon,md-tabs.md-default-theme.md-hue-3 .md-tab.md-focused, md-tabs.md-hue-3 .md-tab.md-focused,md-tabs.md-default-theme.md-hue-3 .md-tab.md-focused md-icon, md-tabs.md-hue-3 .md-tab.md-focused md-icon{color:rgb(68,138,255)}md-tabs.md-default-theme.md-hue-3 .md-tab.md-focused, md-tabs.md-hue-3 .md-tab.md-focused{background:rgba(68,138,255,0.1)}md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper, md-tabs.md-hue-3.md-primary>md-tabs-wrapper{background-color:rgb(68,138,255)}md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper{background-color:rgb(68,138,255)}md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toast.md-default-theme.md-hue-3 .md-toast-content .md-button.md-highlight.md-primary, md-toast.md-hue-3 .md-toast-content .md-button.md-highlight.md-primary{color:rgb(68,138,255)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar), md-toolbar.md-hue-3:not(.md-menu-toolbar){background-color:rgb(68,138,255);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar) md-icon, md-toolbar.md-hue-3:not(.md-menu-toolbar) md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar.md-hue-3:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -.md-button.md-default-theme.md-fab md-icon, .md-button.md-fab md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-fab, .md-button.md-fab{background-color:rgb(21,101,192);color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-fab:not([disabled]) .md-icon, .md-button.md-fab:not([disabled]) .md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-fab:not([disabled]).md-focused, .md-button.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-fab:not([disabled]):hover, .md-button.md-fab:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-accent, .md-button.md-accent{color:rgb(21,101,192)}.md-button.md-default-theme.md-accent.md-fab, .md-button.md-accent.md-fab,.md-button.md-default-theme.md-accent.md-raised, .md-button.md-accent.md-raised{color:rgba(255,255,255,0.87);background-color:rgb(21,101,192)}.md-button.md-default-theme.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-accent.md-raised:not([disabled]) md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-accent.md-fab:not([disabled]):hover, .md-button.md-accent.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-accent.md-raised:not([disabled]):hover, .md-button.md-accent.md-raised:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-accent:not([disabled]) md-icon, .md-button.md-accent:not([disabled]) md-icon{color:rgb(21,101,192)}.md-button.md-default-theme.md-accent[disabled], .md-button.md-accent[disabled],.md-button.md-default-theme.md-fab[disabled], .md-button.md-fab[disabled],.md-button.md-default-theme.md-raised[disabled], .md-button.md-raised[disabled],.md-button.md-default-theme.md-warn[disabled], .md-button.md-warn[disabled],.md-button.md-default-theme[disabled], .md-button[disabled]{color:rgba(0,0,0,0.38);cursor:default}.md-button.md-default-theme.md-accent[disabled] md-icon, .md-button.md-accent[disabled] md-icon,.md-button.md-default-theme.md-fab[disabled] md-icon, .md-button.md-fab[disabled] md-icon,.md-button.md-default-theme.md-raised[disabled] md-icon, .md-button.md-raised[disabled] md-icon,.md-button.md-default-theme.md-warn[disabled] md-icon, .md-button.md-warn[disabled] md-icon,.md-button.md-default-theme[disabled] md-icon, .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}._md a.md-default-theme:not(.md-button).md-accent, ._md a:not(.md-button).md-accent{color:rgb(21,101,192)}._md a.md-default-theme:not(.md-button).md-accent:hover, ._md a:not(.md-button).md-accent:hover{color:rgb(41,98,255)}md-checkbox.md-default-theme .md-ripple, md-checkbox .md-ripple{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-checked.md-focused .md-container:before, md-checkbox.md-checked.md-focused .md-container:before{background-color:rgba(21,101,192,0.26)}md-checkbox.md-default-theme.md-checked .md-ink-ripple, md-checkbox.md-checked .md-ink-ripple{color:rgba(21,101,192,0.87)}md-checkbox.md-default-theme.md-checked .md-icon, md-checkbox.md-checked .md-icon{background-color:rgba(21,101,192,0.87)}md-checkbox.md-default-theme.md-checked .md-icon:after, md-checkbox.md-checked .md-icon:after{border-color:rgba(255,255,255,0.87)}.md-accent .md-default-theme .md-datepicker-input-container.md-datepicker-focused, .md-accent .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(21,101,192)}.md-accent .md-default-theme .md-datepicker-open .md-datepicker-calendar-icon, .md-accent .md-datepicker-open .md-datepicker-calendar-icon,.md-default-theme .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:rgb(21,101,192)}md-icon.md-default-theme.md-accent, md-icon.md-accent{color:rgb(21,101,192)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input, md-input-container:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:rgb(21,101,192)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-accent label, md-input-container:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-accent md-icon, md-input-container:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:rgb(21,101,192)}md-list.md-default-theme md-list-item>md-icon.md-highlight.md-accent, md-list md-list-item>md-icon.md-highlight.md-accent{color:rgb(21,101,192)}md-nav-bar.md-default-theme md-nav-ink-bar, md-nav-bar md-nav-ink-bar{color:rgb(21,101,192);background:rgb(21,101,192)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar, md-nav-bar.md-accent>.md-nav-bar{background-color:rgb(21,101,192)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-accent>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar md-nav-ink-bar, md-nav-bar.md-accent>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar, md-toolbar.md-accent>md-nav-bar>.md-nav-bar{background-color:rgb(21,101,192)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-accent>md-nav-bar>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-accent>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar md-nav-ink-bar, md-toolbar.md-accent>md-nav-bar>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-progress-circular.md-default-theme.md-accent path, md-progress-circular.md-accent path{stroke:rgb(21,101,192)}md-progress-linear.md-default-theme.md-accent .md-container, md-progress-linear.md-accent .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-accent .md-bar, md-progress-linear.md-accent .md-bar{background-color:rgb(21,101,192)}md-progress-linear.md-default-theme[md-mode=buffer].md-accent .md-bar1, md-progress-linear[md-mode=buffer].md-accent .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme[md-mode=buffer].md-accent .md-dashed:before, md-progress-linear[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme .md-on, md-radio-button .md-on{background-color:rgba(21,101,192,0.87)}md-radio-button.md-default-theme.md-checked .md-off, md-radio-button.md-checked .md-off{border-color:rgba(21,101,192,0.87)}md-radio-button.md-default-theme.md-checked .md-ink-ripple, md-radio-button.md-checked .md-ink-ripple{color:rgba(21,101,192,0.87)}md-radio-button.md-default-theme .md-container .md-ripple, md-radio-button .md-container .md-ripple{color:rgb(41,98,255)}md-radio-group.md-default-theme .md-checked .md-ink-ripple, md-radio-group .md-checked .md-ink-ripple{color:rgba(21,101,192,0.26)}md-radio-group.md-default-theme.md-focused:not(:empty) .md-checked .md-container:before, md-radio-group.md-focused:not(:empty) .md-checked .md-container:before{background-color:rgba(21,101,192,0.26)}md-select.md-default-theme:not([disabled]):focus.md-accent .md-select-value, md-select:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:rgb(21,101,192)}md-select-menu.md-default-theme md-content md-option[selected].md-accent, md-select-menu md-content md-option[selected].md-accent{color:rgb(21,101,192)}md-select-menu.md-default-theme md-content md-option[selected].md-accent:focus, md-select-menu md-content md-option[selected].md-accent:focus{color:rgb(41,98,255)}md-slider.md-default-theme .md-focus-ring, md-slider .md-focus-ring{background-color:rgba(68,138,255,0.2)}md-slider.md-default-theme .md-track.md-track-fill, md-slider .md-track.md-track-fill{background-color:rgb(21,101,192)}md-slider.md-default-theme .md-thumb:after, md-slider .md-thumb:after{border-color:rgb(21,101,192);background-color:rgb(21,101,192)}md-slider.md-default-theme .md-sign, md-slider .md-sign{background-color:rgb(21,101,192)}md-slider.md-default-theme .md-sign:after, md-slider .md-sign:after{border-top-color:rgb(21,101,192)}md-slider.md-default-theme[md-vertical] .md-sign:after, md-slider[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(21,101,192)}md-slider.md-default-theme .md-thumb-text, md-slider .md-thumb-text{color:rgba(255,255,255,0.87)}.md-subheader.md-default-theme.md-accent, .md-subheader.md-accent{color:rgb(21,101,192)}md-switch.md-default-theme.md-checked .md-ink-ripple, md-switch.md-checked .md-ink-ripple{color:rgb(21,101,192)}md-switch.md-default-theme.md-checked .md-thumb, md-switch.md-checked .md-thumb{background-color:rgb(21,101,192)}md-switch.md-default-theme.md-checked .md-bar, md-switch.md-checked .md-bar{background-color:rgba(21,101,192,0.5)}md-switch.md-default-theme.md-checked.md-focused .md-thumb:before, md-switch.md-checked.md-focused .md-thumb:before{background-color:rgba(21,101,192,0.26)}md-tabs.md-default-theme md-ink-bar, md-tabs md-ink-bar{color:rgb(21,101,192);background:rgb(21,101,192)}md-tabs.md-default-theme .md-tab .md-ripple-container, md-tabs .md-tab .md-ripple-container{color:rgb(130,177,255)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper, md-tabs.md-accent>md-tabs-wrapper{background-color:rgb(21,101,192)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper, md-toolbar.md-accent>md-tabs>md-tabs-wrapper{background-color:rgb(21,101,192)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toast.md-default-theme .md-toast-content .md-button.md-highlight, md-toast .md-toast-content .md-button.md-highlight{color:rgb(21,101,192)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-accent, md-toolbar:not(.md-menu-toolbar).md-accent{background-color:rgb(21,101,192);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-accent .md-ink-ripple, md-toolbar:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-accent md-icon, md-toolbar:not(.md-menu-toolbar).md-accent md-icon{color:rgba(255,255,255,0.87);fill:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon, md-toolbar:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:rgba(255,255,255,0.26);fill:rgba(255,255,255,0.26)} -.md-button.md-default-theme.md-hue-1.md-fab md-icon, .md-button.md-hue-1.md-fab md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-fab, .md-button.md-hue-1.md-fab{background-color:rgb(227,242,253);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-fab:not([disabled]) .md-icon, .md-button.md-hue-1.md-fab:not([disabled]) .md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-fab:not([disabled]).md-focused, .md-button.md-hue-1.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-fab:not([disabled]):hover, .md-button.md-hue-1.md-fab:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-1.md-accent, .md-button.md-hue-1.md-accent{color:rgb(227,242,253)}.md-button.md-default-theme.md-hue-1.md-accent.md-fab, .md-button.md-hue-1.md-accent.md-fab,.md-button.md-default-theme.md-hue-1.md-accent.md-raised, .md-button.md-hue-1.md-accent.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(227,242,253)}.md-button.md-default-theme.md-hue-1.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-hue-1.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-1.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-hue-1.md-accent.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-hue-1.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-accent.md-fab:not([disabled]):hover, .md-button.md-hue-1.md-accent.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-1.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-hue-1.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-accent.md-raised:not([disabled]):hover, .md-button.md-hue-1.md-accent.md-raised:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-1.md-accent:not([disabled]) md-icon, .md-button.md-hue-1.md-accent:not([disabled]) md-icon{color:rgb(227,242,253)}.md-button.md-default-theme.md-hue-1.md-accent[disabled], .md-button.md-hue-1.md-accent[disabled],.md-button.md-default-theme.md-hue-1.md-fab[disabled], .md-button.md-hue-1.md-fab[disabled],.md-button.md-default-theme.md-hue-1.md-raised[disabled], .md-button.md-hue-1.md-raised[disabled],.md-button.md-default-theme.md-hue-1.md-warn[disabled], .md-button.md-hue-1.md-warn[disabled],.md-button.md-default-theme.md-hue-1[disabled], .md-button.md-hue-1[disabled]{color:rgba(0,0,0,0.38);cursor:default}.md-button.md-default-theme.md-hue-1.md-accent[disabled] md-icon, .md-button.md-hue-1.md-accent[disabled] md-icon,.md-button.md-default-theme.md-hue-1.md-fab[disabled] md-icon, .md-button.md-hue-1.md-fab[disabled] md-icon,.md-button.md-default-theme.md-hue-1.md-raised[disabled] md-icon, .md-button.md-hue-1.md-raised[disabled] md-icon,.md-button.md-default-theme.md-hue-1.md-warn[disabled] md-icon, .md-button.md-hue-1.md-warn[disabled] md-icon,.md-button.md-default-theme.md-hue-1[disabled] md-icon, .md-button.md-hue-1[disabled] md-icon{color:rgba(0,0,0,0.38)}._md a.md-default-theme.md-hue-1:not(.md-button).md-accent, ._md a.md-hue-1:not(.md-button).md-accent{color:rgb(227,242,253)}._md a.md-default-theme.md-hue-1:not(.md-button).md-accent:hover, ._md a.md-hue-1:not(.md-button).md-accent:hover{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-1 .md-ripple, md-checkbox.md-hue-1 .md-ripple{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-1.md-checked.md-focused .md-container:before, md-checkbox.md-hue-1.md-checked.md-focused .md-container:before{background-color:rgba(227,242,253,0.26)}md-checkbox.md-default-theme.md-hue-1.md-checked .md-ink-ripple, md-checkbox.md-hue-1.md-checked .md-ink-ripple{color:rgba(227,242,253,0.87)}md-checkbox.md-default-theme.md-hue-1.md-checked .md-icon, md-checkbox.md-hue-1.md-checked .md-icon{background-color:rgba(227,242,253,0.87)}md-checkbox.md-default-theme.md-hue-1.md-checked .md-icon:after, md-checkbox.md-hue-1.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-accent .md-default-theme.md-hue-1 .md-datepicker-input-container.md-datepicker-focused, .md-accent .md-hue-1 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(227,242,253)}.md-accent .md-default-theme.md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon, .md-accent .md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon,.md-default-theme.md-hue-1 .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-hue-1 .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:rgb(227,242,253)}md-icon.md-default-theme.md-hue-1.md-accent, md-icon.md-hue-1.md-accent{color:rgb(227,242,253)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent .md-input, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:rgb(227,242,253)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent label, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent md-icon, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:rgb(227,242,253)}md-list.md-default-theme.md-hue-1 md-list-item>md-icon.md-highlight.md-accent, md-list.md-hue-1 md-list-item>md-icon.md-highlight.md-accent{color:rgb(227,242,253)}md-nav-bar.md-default-theme.md-hue-1 md-nav-ink-bar, md-nav-bar.md-hue-1 md-nav-ink-bar{color:rgb(227,242,253);background:rgb(227,242,253)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar, md-nav-bar.md-hue-1.md-accent>.md-nav-bar{background-color:rgb(227,242,253)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar md-nav-ink-bar, md-nav-bar.md-hue-1.md-accent>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar{background-color:rgb(227,242,253)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar md-nav-ink-bar, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-progress-circular.md-default-theme.md-hue-1.md-accent path, md-progress-circular.md-hue-1.md-accent path{stroke:rgb(227,242,253)}md-progress-linear.md-default-theme.md-hue-1.md-accent .md-container, md-progress-linear.md-hue-1.md-accent .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-1.md-accent .md-bar, md-progress-linear.md-hue-1.md-accent .md-bar{background-color:rgb(227,242,253)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-accent .md-bar1, md-progress-linear.md-hue-1[md-mode=buffer].md-accent .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-accent .md-dashed:before, md-progress-linear.md-hue-1[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-1 .md-on, md-radio-button.md-hue-1 .md-on{background-color:rgba(227,242,253,0.87)}md-radio-button.md-default-theme.md-hue-1.md-checked .md-off, md-radio-button.md-hue-1.md-checked .md-off{border-color:rgba(227,242,253,0.87)}md-radio-button.md-default-theme.md-hue-1.md-checked .md-ink-ripple, md-radio-button.md-hue-1.md-checked .md-ink-ripple{color:rgba(227,242,253,0.87)}md-radio-button.md-default-theme.md-hue-1 .md-container .md-ripple, md-radio-button.md-hue-1 .md-container .md-ripple{color:rgb(41,98,255)}md-radio-group.md-default-theme.md-hue-1 .md-checked .md-ink-ripple, md-radio-group.md-hue-1 .md-checked .md-ink-ripple{color:rgba(227,242,253,0.26)}md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty) .md-checked .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty) .md-checked .md-container:before{background-color:rgba(227,242,253,0.26)}md-select.md-default-theme.md-hue-1:not([disabled]):focus.md-accent .md-select-value, md-select.md-hue-1:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:rgb(227,242,253)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[selected].md-accent, md-select-menu.md-hue-1 md-content md-option[selected].md-accent{color:rgb(227,242,253)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[selected].md-accent:focus, md-select-menu.md-hue-1 md-content md-option[selected].md-accent:focus{color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-1 .md-focus-ring, md-slider.md-hue-1 .md-focus-ring{background-color:rgba(68,138,255,0.2)}md-slider.md-default-theme.md-hue-1 .md-track.md-track-fill, md-slider.md-hue-1 .md-track.md-track-fill{background-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1 .md-thumb:after, md-slider.md-hue-1 .md-thumb:after{border-color:rgb(227,242,253);background-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1 .md-sign, md-slider.md-hue-1 .md-sign{background-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1 .md-sign:after, md-slider.md-hue-1 .md-sign:after{border-top-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1[md-vertical] .md-sign:after, md-slider.md-hue-1[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1 .md-thumb-text, md-slider.md-hue-1 .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-1.md-accent, .md-subheader.md-hue-1.md-accent{color:rgb(227,242,253)}md-switch.md-default-theme.md-hue-1.md-checked .md-ink-ripple, md-switch.md-hue-1.md-checked .md-ink-ripple{color:rgb(227,242,253)}md-switch.md-default-theme.md-hue-1.md-checked .md-thumb, md-switch.md-hue-1.md-checked .md-thumb{background-color:rgb(227,242,253)}md-switch.md-default-theme.md-hue-1.md-checked .md-bar, md-switch.md-hue-1.md-checked .md-bar{background-color:rgba(227,242,253,0.5)}md-switch.md-default-theme.md-hue-1.md-checked.md-focused .md-thumb:before, md-switch.md-hue-1.md-checked.md-focused .md-thumb:before{background-color:rgba(227,242,253,0.26)}md-tabs.md-default-theme.md-hue-1 md-ink-bar, md-tabs.md-hue-1 md-ink-bar{color:rgb(227,242,253);background:rgb(227,242,253)}md-tabs.md-default-theme.md-hue-1 .md-tab .md-ripple-container, md-tabs.md-hue-1 .md-tab .md-ripple-container{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper, md-tabs.md-hue-1.md-accent>md-tabs-wrapper{background-color:rgb(227,242,253)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper{background-color:rgb(227,242,253)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toast.md-default-theme.md-hue-1 .md-toast-content .md-button.md-highlight, md-toast.md-hue-1 .md-toast-content .md-button.md-highlight{color:rgb(227,242,253)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-accent, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent{background-color:rgb(227,242,253);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-accent .md-ink-ripple, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-accent md-icon, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -.md-button.md-default-theme.md-hue-2.md-fab md-icon, .md-button.md-hue-2.md-fab md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-fab, .md-button.md-hue-2.md-fab{background-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-fab:not([disabled]) .md-icon, .md-button.md-hue-2.md-fab:not([disabled]) .md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-fab:not([disabled]).md-focused, .md-button.md-hue-2.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-fab:not([disabled]):hover, .md-button.md-hue-2.md-fab:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-2.md-accent, .md-button.md-hue-2.md-accent{color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-2.md-accent.md-fab, .md-button.md-hue-2.md-accent.md-fab,.md-button.md-default-theme.md-hue-2.md-accent.md-raised, .md-button.md-hue-2.md-accent.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-2.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-hue-2.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-2.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-hue-2.md-accent.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-hue-2.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-accent.md-fab:not([disabled]):hover, .md-button.md-hue-2.md-accent.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-2.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-hue-2.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-accent.md-raised:not([disabled]):hover, .md-button.md-hue-2.md-accent.md-raised:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-2.md-accent:not([disabled]) md-icon, .md-button.md-hue-2.md-accent:not([disabled]) md-icon{color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-2.md-accent[disabled], .md-button.md-hue-2.md-accent[disabled],.md-button.md-default-theme.md-hue-2.md-fab[disabled], .md-button.md-hue-2.md-fab[disabled],.md-button.md-default-theme.md-hue-2.md-raised[disabled], .md-button.md-hue-2.md-raised[disabled],.md-button.md-default-theme.md-hue-2.md-warn[disabled], .md-button.md-hue-2.md-warn[disabled],.md-button.md-default-theme.md-hue-2[disabled], .md-button.md-hue-2[disabled]{color:rgba(0,0,0,0.38);cursor:default}.md-button.md-default-theme.md-hue-2.md-accent[disabled] md-icon, .md-button.md-hue-2.md-accent[disabled] md-icon,.md-button.md-default-theme.md-hue-2.md-fab[disabled] md-icon, .md-button.md-hue-2.md-fab[disabled] md-icon,.md-button.md-default-theme.md-hue-2.md-raised[disabled] md-icon, .md-button.md-hue-2.md-raised[disabled] md-icon,.md-button.md-default-theme.md-hue-2.md-warn[disabled] md-icon, .md-button.md-hue-2.md-warn[disabled] md-icon,.md-button.md-default-theme.md-hue-2[disabled] md-icon, .md-button.md-hue-2[disabled] md-icon{color:rgba(0,0,0,0.38)}._md a.md-default-theme.md-hue-2:not(.md-button).md-accent, ._md a.md-hue-2:not(.md-button).md-accent{color:rgb(33,150,243)}._md a.md-default-theme.md-hue-2:not(.md-button).md-accent:hover, ._md a.md-hue-2:not(.md-button).md-accent:hover{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-2 .md-ripple, md-checkbox.md-hue-2 .md-ripple{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-2.md-checked.md-focused .md-container:before, md-checkbox.md-hue-2.md-checked.md-focused .md-container:before{background-color:rgba(33,150,243,0.26)}md-checkbox.md-default-theme.md-hue-2.md-checked .md-ink-ripple, md-checkbox.md-hue-2.md-checked .md-ink-ripple{color:rgba(33,150,243,0.87)}md-checkbox.md-default-theme.md-hue-2.md-checked .md-icon, md-checkbox.md-hue-2.md-checked .md-icon{background-color:rgba(33,150,243,0.87)}md-checkbox.md-default-theme.md-hue-2.md-checked .md-icon:after, md-checkbox.md-hue-2.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-accent .md-default-theme.md-hue-2 .md-datepicker-input-container.md-datepicker-focused, .md-accent .md-hue-2 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(33,150,243)}.md-accent .md-default-theme.md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon, .md-accent .md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon,.md-default-theme.md-hue-2 .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-hue-2 .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:rgb(33,150,243)}md-icon.md-default-theme.md-hue-2.md-accent, md-icon.md-hue-2.md-accent{color:rgb(33,150,243)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent .md-input, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:rgb(33,150,243)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent label, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent md-icon, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:rgb(33,150,243)}md-list.md-default-theme.md-hue-2 md-list-item>md-icon.md-highlight.md-accent, md-list.md-hue-2 md-list-item>md-icon.md-highlight.md-accent{color:rgb(33,150,243)}md-nav-bar.md-default-theme.md-hue-2 md-nav-ink-bar, md-nav-bar.md-hue-2 md-nav-ink-bar{color:rgb(33,150,243);background:rgb(33,150,243)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar, md-nav-bar.md-hue-2.md-accent>.md-nav-bar{background-color:rgb(33,150,243)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar md-nav-ink-bar, md-nav-bar.md-hue-2.md-accent>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar{background-color:rgb(33,150,243)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar md-nav-ink-bar, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-progress-circular.md-default-theme.md-hue-2.md-accent path, md-progress-circular.md-hue-2.md-accent path{stroke:rgb(33,150,243)}md-progress-linear.md-default-theme.md-hue-2.md-accent .md-container, md-progress-linear.md-hue-2.md-accent .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-2.md-accent .md-bar, md-progress-linear.md-hue-2.md-accent .md-bar{background-color:rgb(33,150,243)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-accent .md-bar1, md-progress-linear.md-hue-2[md-mode=buffer].md-accent .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-accent .md-dashed:before, md-progress-linear.md-hue-2[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-2 .md-on, md-radio-button.md-hue-2 .md-on{background-color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-2.md-checked .md-off, md-radio-button.md-hue-2.md-checked .md-off{border-color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-2.md-checked .md-ink-ripple, md-radio-button.md-hue-2.md-checked .md-ink-ripple{color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-2 .md-container .md-ripple, md-radio-button.md-hue-2 .md-container .md-ripple{color:rgb(41,98,255)}md-radio-group.md-default-theme.md-hue-2 .md-checked .md-ink-ripple, md-radio-group.md-hue-2 .md-checked .md-ink-ripple{color:rgba(33,150,243,0.26)}md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty) .md-checked .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty) .md-checked .md-container:before{background-color:rgba(33,150,243,0.26)}md-select.md-default-theme.md-hue-2:not([disabled]):focus.md-accent .md-select-value, md-select.md-hue-2:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[selected].md-accent, md-select-menu.md-hue-2 md-content md-option[selected].md-accent{color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[selected].md-accent:focus, md-select-menu.md-hue-2 md-content md-option[selected].md-accent:focus{color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-2 .md-focus-ring, md-slider.md-hue-2 .md-focus-ring{background-color:rgba(68,138,255,0.2)}md-slider.md-default-theme.md-hue-2 .md-track.md-track-fill, md-slider.md-hue-2 .md-track.md-track-fill{background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2 .md-thumb:after, md-slider.md-hue-2 .md-thumb:after{border-color:rgb(33,150,243);background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2 .md-sign, md-slider.md-hue-2 .md-sign{background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2 .md-sign:after, md-slider.md-hue-2 .md-sign:after{border-top-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2[md-vertical] .md-sign:after, md-slider.md-hue-2[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2 .md-thumb-text, md-slider.md-hue-2 .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-2.md-accent, .md-subheader.md-hue-2.md-accent{color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-2.md-checked .md-ink-ripple, md-switch.md-hue-2.md-checked .md-ink-ripple{color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-2.md-checked .md-thumb, md-switch.md-hue-2.md-checked .md-thumb{background-color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-2.md-checked .md-bar, md-switch.md-hue-2.md-checked .md-bar{background-color:rgba(33,150,243,0.5)}md-switch.md-default-theme.md-hue-2.md-checked.md-focused .md-thumb:before, md-switch.md-hue-2.md-checked.md-focused .md-thumb:before{background-color:rgba(33,150,243,0.26)}md-tabs.md-default-theme.md-hue-2 md-ink-bar, md-tabs.md-hue-2 md-ink-bar{color:rgb(33,150,243);background:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-2 .md-tab .md-ripple-container, md-tabs.md-hue-2 .md-tab .md-ripple-container{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper, md-tabs.md-hue-2.md-accent>md-tabs-wrapper{background-color:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper{background-color:rgb(33,150,243)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toast.md-default-theme.md-hue-2 .md-toast-content .md-button.md-highlight, md-toast.md-hue-2 .md-toast-content .md-button.md-highlight{color:rgb(33,150,243)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-accent, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-accent{background-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-accent .md-ink-ripple, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-accent md-icon, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-accent md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -.md-button.md-default-theme.md-hue-3.md-fab md-icon, .md-button.md-hue-3.md-fab md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-fab, .md-button.md-hue-3.md-fab{background-color:rgb(41,98,255);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-fab:not([disabled]) .md-icon, .md-button.md-hue-3.md-fab:not([disabled]) .md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-fab:not([disabled]).md-focused, .md-button.md-hue-3.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-fab:not([disabled]):hover, .md-button.md-hue-3.md-fab:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent, .md-button.md-hue-3.md-accent{color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent.md-fab, .md-button.md-hue-3.md-accent.md-fab,.md-button.md-default-theme.md-hue-3.md-accent.md-raised, .md-button.md-hue-3.md-accent.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-hue-3.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-3.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-hue-3.md-accent.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-hue-3.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-accent.md-fab:not([disabled]):hover, .md-button.md-hue-3.md-accent.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-3.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-hue-3.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-accent.md-raised:not([disabled]):hover, .md-button.md-hue-3.md-accent.md-raised:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent:not([disabled]) md-icon, .md-button.md-hue-3.md-accent:not([disabled]) md-icon{color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent[disabled], .md-button.md-hue-3.md-accent[disabled],.md-button.md-default-theme.md-hue-3.md-fab[disabled], .md-button.md-hue-3.md-fab[disabled],.md-button.md-default-theme.md-hue-3.md-raised[disabled], .md-button.md-hue-3.md-raised[disabled],.md-button.md-default-theme.md-hue-3.md-warn[disabled], .md-button.md-hue-3.md-warn[disabled],.md-button.md-default-theme.md-hue-3[disabled], .md-button.md-hue-3[disabled]{color:rgba(0,0,0,0.38);cursor:default}.md-button.md-default-theme.md-hue-3.md-accent[disabled] md-icon, .md-button.md-hue-3.md-accent[disabled] md-icon,.md-button.md-default-theme.md-hue-3.md-fab[disabled] md-icon, .md-button.md-hue-3.md-fab[disabled] md-icon,.md-button.md-default-theme.md-hue-3.md-raised[disabled] md-icon, .md-button.md-hue-3.md-raised[disabled] md-icon,.md-button.md-default-theme.md-hue-3.md-warn[disabled] md-icon, .md-button.md-hue-3.md-warn[disabled] md-icon,.md-button.md-default-theme.md-hue-3[disabled] md-icon, .md-button.md-hue-3[disabled] md-icon{color:rgba(0,0,0,0.38)}._md a.md-default-theme.md-hue-3:not(.md-button).md-accent, ._md a.md-hue-3:not(.md-button).md-accent{color:rgb(41,98,255)}._md a.md-default-theme.md-hue-3:not(.md-button).md-accent:hover, ._md a.md-hue-3:not(.md-button).md-accent:hover{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-3 .md-ripple, md-checkbox.md-hue-3 .md-ripple{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-3.md-checked.md-focused .md-container:before, md-checkbox.md-hue-3.md-checked.md-focused .md-container:before{background-color:rgba(41,98,255,0.26)}md-checkbox.md-default-theme.md-hue-3.md-checked .md-ink-ripple, md-checkbox.md-hue-3.md-checked .md-ink-ripple{color:rgba(41,98,255,0.87)}md-checkbox.md-default-theme.md-hue-3.md-checked .md-icon, md-checkbox.md-hue-3.md-checked .md-icon{background-color:rgba(41,98,255,0.87)}md-checkbox.md-default-theme.md-hue-3.md-checked .md-icon:after, md-checkbox.md-hue-3.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-accent .md-default-theme.md-hue-3 .md-datepicker-input-container.md-datepicker-focused, .md-accent .md-hue-3 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(41,98,255)}.md-accent .md-default-theme.md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon, .md-accent .md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon,.md-default-theme.md-hue-3 .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-hue-3 .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:rgb(41,98,255)}md-icon.md-default-theme.md-hue-3.md-accent, md-icon.md-hue-3.md-accent{color:rgb(41,98,255)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent .md-input, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:rgb(41,98,255)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent label, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent md-icon, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:rgb(41,98,255)}md-list.md-default-theme.md-hue-3 md-list-item>md-icon.md-highlight.md-accent, md-list.md-hue-3 md-list-item>md-icon.md-highlight.md-accent{color:rgb(41,98,255)}md-nav-bar.md-default-theme.md-hue-3 md-nav-ink-bar, md-nav-bar.md-hue-3 md-nav-ink-bar{color:rgb(41,98,255);background:rgb(41,98,255)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar, md-nav-bar.md-hue-3.md-accent>.md-nav-bar{background-color:rgb(41,98,255)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar md-nav-ink-bar, md-nav-bar.md-hue-3.md-accent>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar{background-color:rgb(41,98,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar md-nav-ink-bar, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-progress-circular.md-default-theme.md-hue-3.md-accent path, md-progress-circular.md-hue-3.md-accent path{stroke:rgb(41,98,255)}md-progress-linear.md-default-theme.md-hue-3.md-accent .md-container, md-progress-linear.md-hue-3.md-accent .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-3.md-accent .md-bar, md-progress-linear.md-hue-3.md-accent .md-bar{background-color:rgb(41,98,255)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-accent .md-bar1, md-progress-linear.md-hue-3[md-mode=buffer].md-accent .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-accent .md-dashed:before, md-progress-linear.md-hue-3[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-3 .md-on, md-radio-button.md-hue-3 .md-on{background-color:rgba(41,98,255,0.87)}md-radio-button.md-default-theme.md-hue-3.md-checked .md-off, md-radio-button.md-hue-3.md-checked .md-off{border-color:rgba(41,98,255,0.87)}md-radio-button.md-default-theme.md-hue-3.md-checked .md-ink-ripple, md-radio-button.md-hue-3.md-checked .md-ink-ripple{color:rgba(41,98,255,0.87)}md-radio-button.md-default-theme.md-hue-3 .md-container .md-ripple, md-radio-button.md-hue-3 .md-container .md-ripple{color:rgb(41,98,255)}md-radio-group.md-default-theme.md-hue-3 .md-checked .md-ink-ripple, md-radio-group.md-hue-3 .md-checked .md-ink-ripple{color:rgba(41,98,255,0.26)}md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty) .md-checked .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty) .md-checked .md-container:before{background-color:rgba(41,98,255,0.26)}md-select.md-default-theme.md-hue-3:not([disabled]):focus.md-accent .md-select-value, md-select.md-hue-3:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:rgb(41,98,255)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[selected].md-accent, md-select-menu.md-hue-3 md-content md-option[selected].md-accent{color:rgb(41,98,255)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[selected].md-accent:focus, md-select-menu.md-hue-3 md-content md-option[selected].md-accent:focus{color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-focus-ring, md-slider.md-hue-3 .md-focus-ring{background-color:rgba(68,138,255,0.2)}md-slider.md-default-theme.md-hue-3 .md-track.md-track-fill, md-slider.md-hue-3 .md-track.md-track-fill{background-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-thumb:after, md-slider.md-hue-3 .md-thumb:after{border-color:rgb(41,98,255);background-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-sign, md-slider.md-hue-3 .md-sign{background-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-sign:after, md-slider.md-hue-3 .md-sign:after{border-top-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3[md-vertical] .md-sign:after, md-slider.md-hue-3[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-thumb-text, md-slider.md-hue-3 .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-3.md-accent, .md-subheader.md-hue-3.md-accent{color:rgb(41,98,255)}md-switch.md-default-theme.md-hue-3.md-checked .md-ink-ripple, md-switch.md-hue-3.md-checked .md-ink-ripple{color:rgb(41,98,255)}md-switch.md-default-theme.md-hue-3.md-checked .md-thumb, md-switch.md-hue-3.md-checked .md-thumb{background-color:rgb(41,98,255)}md-switch.md-default-theme.md-hue-3.md-checked .md-bar, md-switch.md-hue-3.md-checked .md-bar{background-color:rgba(41,98,255,0.5)}md-switch.md-default-theme.md-hue-3.md-checked.md-focused .md-thumb:before, md-switch.md-hue-3.md-checked.md-focused .md-thumb:before{background-color:rgba(41,98,255,0.26)}md-tabs.md-default-theme.md-hue-3 md-ink-bar, md-tabs.md-hue-3 md-ink-bar{color:rgb(41,98,255);background:rgb(41,98,255)}md-tabs.md-default-theme.md-hue-3 .md-tab .md-ripple-container, md-tabs.md-hue-3 .md-tab .md-ripple-container{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper, md-tabs.md-hue-3.md-accent>md-tabs-wrapper{background-color:rgb(41,98,255)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper{background-color:rgb(41,98,255)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toast.md-default-theme.md-hue-3 .md-toast-content .md-button.md-highlight, md-toast.md-hue-3 .md-toast-content .md-button.md-highlight{color:rgb(41,98,255)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-accent, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-accent{background-color:rgb(41,98,255);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-accent .md-ink-ripple, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-accent md-icon, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-accent md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -.md-button.md-default-theme.md-warn, .md-button.md-warn{color:rgb(255,87,34)}.md-button.md-default-theme.md-warn.md-fab, .md-button.md-warn.md-fab,.md-button.md-default-theme.md-warn.md-raised, .md-button.md-warn.md-raised{color:rgb(255,255,255);background-color:rgb(255,87,34)}.md-button.md-default-theme.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-warn.md-raised:not([disabled]) md-icon{color:rgb(255,255,255)}.md-button.md-default-theme.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-warn.md-fab:not([disabled]):hover, .md-button.md-warn.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-warn.md-raised:not([disabled]):hover, .md-button.md-warn.md-raised:not([disabled]):hover{background-color:rgb(244,81,30)}.md-button.md-default-theme.md-warn:not([disabled]) md-icon, .md-button.md-warn:not([disabled]) md-icon{color:rgb(255,87,34)}._md a.md-default-theme:not(.md-button).md-warn, ._md a:not(.md-button).md-warn{color:rgb(255,87,34)}._md a.md-default-theme:not(.md-button).md-warn:hover, ._md a:not(.md-button).md-warn:hover{color:rgb(230,74,25)}md-checkbox.md-default-theme:not([disabled]).md-warn .md-ripple, md-checkbox:not([disabled]).md-warn .md-ripple{color:rgb(244,81,30)}md-checkbox.md-default-theme:not([disabled]).md-warn .md-ink-ripple, md-checkbox:not([disabled]).md-warn .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-checkbox:not([disabled]).md-warn.md-checked .md-ink-ripple{color:rgba(255,87,34,0.87)}md-checkbox.md-default-theme:not([disabled]).md-warn:not(.md-checked) .md-icon, md-checkbox:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-warn.md-checked .md-icon, md-checkbox:not([disabled]).md-warn.md-checked .md-icon{background-color:rgba(255,87,34,0.87)}md-checkbox.md-default-theme:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before, md-checkbox:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:rgba(255,87,34,0.26)}md-checkbox.md-default-theme:not([disabled]).md-warn.md-checked .md-icon:after, md-checkbox:not([disabled]).md-warn.md-checked .md-icon:after{border-color:rgb(238,238,238)}.md-default-theme .md-datepicker-input-container.md-datepicker-invalid, .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-default-theme .md-datepicker-input-container.md-datepicker-focused, .md-warn .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(221,44,0)}.md-default-theme .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-default-theme .md-datepicker-open .md-datepicker-calendar-icon, .md-warn .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(221,44,0)}md-icon.md-default-theme.md-warn, md-icon.md-warn{color:rgb(255,87,34)}md-input-container.md-default-theme label.md-required:after, md-input-container label.md-required:after{color:rgb(221,44,0)}md-input-container.md-default-theme .md-input-message-animation, md-input-container .md-input-message-animation,md-input-container.md-default-theme .md-input-messages-animation, md-input-container .md-input-messages-animation{color:rgb(221,44,0)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input, md-input-container:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-warn label, md-input-container:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-warn md-icon, md-input-container:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:rgb(221,44,0)}md-input-container.md-default-theme.md-input-invalid .md-input, md-input-container.md-input-invalid .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-input-invalid .md-char-counter, md-input-container.md-input-invalid .md-char-counter,md-input-container.md-default-theme.md-input-invalid .md-input-message-animation, md-input-container.md-input-invalid .md-input-message-animation,md-input-container.md-default-theme.md-input-invalid label, md-input-container.md-input-invalid label{color:rgb(221,44,0)}md-nav-bar.md-default-theme.md-warn>.md-nav-bar, md-nav-bar.md-warn>.md-nav-bar{background-color:rgb(255,87,34)}md-nav-bar.md-default-theme.md-warn>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-warn>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-nav-bar.md-default-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgb(255,255,255)}md-nav-bar.md-default-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar, md-toolbar.md-warn>md-nav-bar>.md-nav-bar{background-color:rgb(255,87,34)}md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-warn>md-nav-bar>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-warn>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgb(255,255,255)}md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-progress-circular.md-default-theme.md-warn path, md-progress-circular.md-warn path{stroke:rgb(255,87,34)}md-progress-linear.md-default-theme.md-warn .md-container, md-progress-linear.md-warn .md-container{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-warn .md-bar, md-progress-linear.md-warn .md-bar{background-color:rgb(255,87,34)}md-progress-linear.md-default-theme[md-mode=buffer].md-warn .md-bar1, md-progress-linear[md-mode=buffer].md-warn .md-bar1{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme[md-mode=buffer].md-warn .md-dashed:before, md-progress-linear[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(rgb(255,204,188) 0,rgb(255,204,188) 16%,transparent 42%)}md-radio-button.md-default-theme:not([disabled]).md-warn .md-on, md-radio-button:not([disabled]).md-warn .md-on,md-radio-button.md-default-theme:not([disabled]) .md-warn .md-on, md-radio-button:not([disabled]) .md-warn .md-on,md-radio-group.md-default-theme:not([disabled]).md-warn .md-on, md-radio-group:not([disabled]).md-warn .md-on,md-radio-group.md-default-theme:not([disabled]) .md-warn .md-on, md-radio-group:not([disabled]) .md-warn .md-on{background-color:rgba(255,87,34,0.87)}md-radio-button.md-default-theme:not([disabled]).md-warn.md-checked .md-off, md-radio-button:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-default-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-button:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-default-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-button:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-default-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-button:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-default-theme:not([disabled]).md-warn.md-checked .md-off, md-radio-group:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-default-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-group:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-default-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-group:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-default-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-group:not([disabled]) .md-warn .md-checked .md-off{border-color:rgba(255,87,34,0.87)}md-radio-button.md-default-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-group:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:rgba(255,87,34,0.87)}md-radio-button.md-default-theme:not([disabled]).md-warn .md-container .md-ripple, md-radio-button:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-default-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-default-theme:not([disabled]).md-warn .md-container .md-ripple, md-radio-group:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-default-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group:not([disabled]) .md-warn .md-container .md-ripple{color:rgb(244,81,30)}md-radio-group.md-default-theme.md-focused:not(:empty) .md-checked.md-warn .md-container:before, md-radio-group.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-default-theme.md-focused:not(:empty).md-warn .md-checked .md-container:before, md-radio-group.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:rgba(255,87,34,0.26)}md-input-container md-select.md-default-theme .md-select-value span:first-child:after, md-input-container md-select .md-select-value span:first-child:after{color:rgb(221,44,0)}md-input-container.md-input-invalid md-select.md-default-theme .md-select-value, md-input-container.md-input-invalid md-select .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme .md-select-value span:first-child:after, md-select .md-select-value span:first-child:after{color:rgb(221,44,0)}md-select.md-default-theme.ng-invalid.ng-touched .md-select-value, md-select.ng-invalid.ng-touched .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme:not([disabled]):focus.md-warn .md-select-value, md-select:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-focus-ring, md-slider.md-warn .md-focus-ring{background-color:rgba(255,171,145,0.38)}md-slider.md-default-theme.md-warn .md-track.md-track-fill, md-slider.md-warn .md-track.md-track-fill{background-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-thumb:after, md-slider.md-warn .md-thumb:after{border-color:rgb(255,87,34);background-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-sign, md-slider.md-warn .md-sign{background-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-sign:after, md-slider.md-warn .md-sign:after{border-top-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn[md-vertical] .md-sign:after, md-slider.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-thumb-text, md-slider.md-warn .md-thumb-text{color:rgb(255,255,255)}.md-subheader.md-default-theme.md-warn, .md-subheader.md-warn{color:rgb(255,87,34)}md-switch.md-default-theme.md-checked.md-warn .md-ink-ripple, md-switch.md-checked.md-warn .md-ink-ripple{color:rgb(255,87,34)}md-switch.md-default-theme.md-checked.md-warn .md-thumb, md-switch.md-checked.md-warn .md-thumb{background-color:rgb(255,87,34)}md-switch.md-default-theme.md-checked.md-warn .md-bar, md-switch.md-checked.md-warn .md-bar{background-color:rgba(255,87,34,0.5)}md-switch.md-default-theme.md-checked.md-warn.md-focused .md-thumb:before, md-switch.md-checked.md-warn.md-focused .md-thumb:before{background-color:rgba(255,87,34,0.26)}md-tabs.md-default-theme.md-warn>md-tabs-wrapper, md-tabs.md-warn>md-tabs-wrapper{background-color:rgb(255,87,34)}md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgb(255,255,255)}md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper, md-toolbar.md-warn>md-tabs>md-tabs-wrapper{background-color:rgb(255,87,34)}md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgb(255,255,255)}md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toast.md-default-theme .md-toast-content .md-button.md-highlight.md-warn, md-toast .md-toast-content .md-button.md-highlight.md-warn{color:rgb(255,87,34)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-warn, md-toolbar:not(.md-menu-toolbar).md-warn{background-color:rgb(255,87,34);color:rgb(255,255,255)} -.md-button.md-default-theme.md-hue-1.md-warn, .md-button.md-hue-1.md-warn{color:rgb(255,138,101)}.md-button.md-default-theme.md-hue-1.md-warn.md-fab, .md-button.md-hue-1.md-warn.md-fab,.md-button.md-default-theme.md-hue-1.md-warn.md-raised, .md-button.md-hue-1.md-warn.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(255,138,101)}.md-button.md-default-theme.md-hue-1.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-hue-1.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-1.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-hue-1.md-warn.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-hue-1.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-warn.md-fab:not([disabled]):hover, .md-button.md-hue-1.md-warn.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-1.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-hue-1.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-warn.md-raised:not([disabled]):hover, .md-button.md-hue-1.md-warn.md-raised:not([disabled]):hover{background-color:rgb(244,81,30)}.md-button.md-default-theme.md-hue-1.md-warn:not([disabled]) md-icon, .md-button.md-hue-1.md-warn:not([disabled]) md-icon{color:rgb(255,138,101)}._md a.md-default-theme.md-hue-1:not(.md-button).md-warn, ._md a.md-hue-1:not(.md-button).md-warn{color:rgb(255,138,101)}._md a.md-default-theme.md-hue-1:not(.md-button).md-warn:hover, ._md a.md-hue-1:not(.md-button).md-warn:hover{color:rgb(230,74,25)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn .md-ripple, md-checkbox.md-hue-1:not([disabled]).md-warn .md-ripple{color:rgb(244,81,30)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn .md-ink-ripple, md-checkbox.md-hue-1:not([disabled]).md-warn .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple, md-checkbox.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple{color:rgba(255,138,101,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn:not(.md-checked) .md-icon, md-checkbox.md-hue-1:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-icon, md-checkbox.md-hue-1:not([disabled]).md-warn.md-checked .md-icon{background-color:rgba(255,138,101,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before, md-checkbox.md-hue-1:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:rgba(255,138,101,0.26)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-icon:after, md-checkbox.md-hue-1:not([disabled]).md-warn.md-checked .md-icon:after{border-color:rgb(238,238,238)}.md-default-theme.md-hue-1 .md-datepicker-input-container.md-datepicker-invalid, .md-hue-1 .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-default-theme.md-hue-1 .md-datepicker-input-container.md-datepicker-focused, .md-warn .md-hue-1 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(221,44,0)}.md-default-theme.md-hue-1 .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-hue-1 .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-default-theme.md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon, .md-warn .md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(221,44,0)}md-icon.md-default-theme.md-hue-1.md-warn, md-icon.md-hue-1.md-warn{color:rgb(255,138,101)}md-input-container.md-default-theme.md-hue-1 label.md-required:after, md-input-container.md-hue-1 label.md-required:after{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1 .md-input-message-animation, md-input-container.md-hue-1 .md-input-message-animation,md-input-container.md-default-theme.md-hue-1 .md-input-messages-animation, md-input-container.md-hue-1 .md-input-messages-animation{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn .md-input, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn label, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn md-icon, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1.md-input-invalid .md-input, md-input-container.md-hue-1.md-input-invalid .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1.md-input-invalid .md-char-counter, md-input-container.md-hue-1.md-input-invalid .md-char-counter,md-input-container.md-default-theme.md-hue-1.md-input-invalid .md-input-message-animation, md-input-container.md-hue-1.md-input-invalid .md-input-message-animation,md-input-container.md-default-theme.md-hue-1.md-input-invalid label, md-input-container.md-hue-1.md-input-invalid label{color:rgb(221,44,0)}md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar, md-nav-bar.md-hue-1.md-warn>.md-nav-bar{background-color:rgb(255,138,101)}md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar{background-color:rgb(255,138,101)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-progress-circular.md-default-theme.md-hue-1.md-warn path, md-progress-circular.md-hue-1.md-warn path{stroke:rgb(255,138,101)}md-progress-linear.md-default-theme.md-hue-1.md-warn .md-container, md-progress-linear.md-hue-1.md-warn .md-container{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-1.md-warn .md-bar, md-progress-linear.md-hue-1.md-warn .md-bar{background-color:rgb(255,138,101)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-warn .md-bar1, md-progress-linear.md-hue-1[md-mode=buffer].md-warn .md-bar1{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-warn .md-dashed:before, md-progress-linear.md-hue-1[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(rgb(255,204,188) 0,rgb(255,204,188) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn .md-on, md-radio-button.md-hue-1:not([disabled]).md-warn .md-on,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-on, md-radio-button.md-hue-1:not([disabled]) .md-warn .md-on,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn .md-on, md-radio-group.md-hue-1:not([disabled]).md-warn .md-on,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-on, md-radio-group.md-hue-1:not([disabled]) .md-warn .md-on{background-color:rgba(255,138,101,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-hue-1:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-hue-1:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-hue-1:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-hue-1:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-off, md-radio-group.md-hue-1:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-hue-1:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-hue-1:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-hue-1:not([disabled]) .md-warn .md-checked .md-off{border-color:rgba(255,138,101,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:rgba(255,138,101,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-hue-1:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-hue-1:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn .md-container .md-ripple, md-radio-group.md-hue-1:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-hue-1:not([disabled]) .md-warn .md-container .md-ripple{color:rgb(244,81,30)}md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty) .md-checked.md-warn .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty).md-warn .md-checked .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:rgba(255,138,101,0.26)}md-input-container md-select.md-default-theme.md-hue-1 .md-select-value span:first-child:after, md-input-container md-select.md-hue-1 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-1 .md-select-value, md-input-container.md-input-invalid md-select.md-hue-1 .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-1 .md-select-value span:first-child:after, md-select.md-hue-1 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-select.md-default-theme.md-hue-1.ng-invalid.ng-touched .md-select-value, md-select.md-hue-1.ng-invalid.ng-touched .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-1:not([disabled]):focus.md-warn .md-select-value, md-select.md-hue-1:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-focus-ring, md-slider.md-hue-1.md-warn .md-focus-ring{background-color:rgba(255,171,145,0.38)}md-slider.md-default-theme.md-hue-1.md-warn .md-track.md-track-fill, md-slider.md-hue-1.md-warn .md-track.md-track-fill{background-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-thumb:after, md-slider.md-hue-1.md-warn .md-thumb:after{border-color:rgb(255,138,101);background-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-sign, md-slider.md-hue-1.md-warn .md-sign{background-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-sign:after, md-slider.md-hue-1.md-warn .md-sign:after{border-top-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn[md-vertical] .md-sign:after, md-slider.md-hue-1.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-thumb-text, md-slider.md-hue-1.md-warn .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-1.md-warn, .md-subheader.md-hue-1.md-warn{color:rgb(255,138,101)}md-switch.md-default-theme.md-hue-1.md-checked.md-warn .md-ink-ripple, md-switch.md-hue-1.md-checked.md-warn .md-ink-ripple{color:rgb(255,138,101)}md-switch.md-default-theme.md-hue-1.md-checked.md-warn .md-thumb, md-switch.md-hue-1.md-checked.md-warn .md-thumb{background-color:rgb(255,138,101)}md-switch.md-default-theme.md-hue-1.md-checked.md-warn .md-bar, md-switch.md-hue-1.md-checked.md-warn .md-bar{background-color:rgba(255,138,101,0.5)}md-switch.md-default-theme.md-hue-1.md-checked.md-warn.md-focused .md-thumb:before, md-switch.md-hue-1.md-checked.md-warn.md-focused .md-thumb:before{background-color:rgba(255,138,101,0.26)}md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper, md-tabs.md-hue-1.md-warn>md-tabs-wrapper{background-color:rgb(255,138,101)}md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper{background-color:rgb(255,138,101)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toast.md-default-theme.md-hue-1 .md-toast-content .md-button.md-highlight.md-warn, md-toast.md-hue-1 .md-toast-content .md-button.md-highlight.md-warn{color:rgb(255,138,101)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-warn, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-warn{background-color:rgb(255,138,101);color:rgba(0,0,0,0.87)} -.md-button.md-default-theme.md-hue-2.md-warn, .md-button.md-hue-2.md-warn{color:rgb(216,67,21)}.md-button.md-default-theme.md-hue-2.md-warn.md-fab, .md-button.md-hue-2.md-warn.md-fab,.md-button.md-default-theme.md-hue-2.md-warn.md-raised, .md-button.md-hue-2.md-warn.md-raised{color:rgb(255,255,255);background-color:rgb(216,67,21)}.md-button.md-default-theme.md-hue-2.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-hue-2.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-2.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-hue-2.md-warn.md-raised:not([disabled]) md-icon{color:rgb(255,255,255)}.md-button.md-default-theme.md-hue-2.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-hue-2.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-warn.md-fab:not([disabled]):hover, .md-button.md-hue-2.md-warn.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-2.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-hue-2.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-warn.md-raised:not([disabled]):hover, .md-button.md-hue-2.md-warn.md-raised:not([disabled]):hover{background-color:rgb(244,81,30)}.md-button.md-default-theme.md-hue-2.md-warn:not([disabled]) md-icon, .md-button.md-hue-2.md-warn:not([disabled]) md-icon{color:rgb(216,67,21)}._md a.md-default-theme.md-hue-2:not(.md-button).md-warn, ._md a.md-hue-2:not(.md-button).md-warn{color:rgb(216,67,21)}._md a.md-default-theme.md-hue-2:not(.md-button).md-warn:hover, ._md a.md-hue-2:not(.md-button).md-warn:hover{color:rgb(230,74,25)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn .md-ripple, md-checkbox.md-hue-2:not([disabled]).md-warn .md-ripple{color:rgb(244,81,30)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn .md-ink-ripple, md-checkbox.md-hue-2:not([disabled]).md-warn .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple, md-checkbox.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple{color:rgba(216,67,21,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn:not(.md-checked) .md-icon, md-checkbox.md-hue-2:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-icon, md-checkbox.md-hue-2:not([disabled]).md-warn.md-checked .md-icon{background-color:rgba(216,67,21,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before, md-checkbox.md-hue-2:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:rgba(216,67,21,0.26)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-icon:after, md-checkbox.md-hue-2:not([disabled]).md-warn.md-checked .md-icon:after{border-color:rgb(238,238,238)}.md-default-theme.md-hue-2 .md-datepicker-input-container.md-datepicker-invalid, .md-hue-2 .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-default-theme.md-hue-2 .md-datepicker-input-container.md-datepicker-focused, .md-warn .md-hue-2 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(221,44,0)}.md-default-theme.md-hue-2 .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-hue-2 .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-default-theme.md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon, .md-warn .md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(221,44,0)}md-icon.md-default-theme.md-hue-2.md-warn, md-icon.md-hue-2.md-warn{color:rgb(216,67,21)}md-input-container.md-default-theme.md-hue-2 label.md-required:after, md-input-container.md-hue-2 label.md-required:after{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2 .md-input-message-animation, md-input-container.md-hue-2 .md-input-message-animation,md-input-container.md-default-theme.md-hue-2 .md-input-messages-animation, md-input-container.md-hue-2 .md-input-messages-animation{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn .md-input, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn label, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn md-icon, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2.md-input-invalid .md-input, md-input-container.md-hue-2.md-input-invalid .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2.md-input-invalid .md-char-counter, md-input-container.md-hue-2.md-input-invalid .md-char-counter,md-input-container.md-default-theme.md-hue-2.md-input-invalid .md-input-message-animation, md-input-container.md-hue-2.md-input-invalid .md-input-message-animation,md-input-container.md-default-theme.md-hue-2.md-input-invalid label, md-input-container.md-hue-2.md-input-invalid label{color:rgb(221,44,0)}md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar, md-nav-bar.md-hue-2.md-warn>.md-nav-bar{background-color:rgb(216,67,21)}md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgb(255,255,255)}md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar{background-color:rgb(216,67,21)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgb(255,255,255)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-progress-circular.md-default-theme.md-hue-2.md-warn path, md-progress-circular.md-hue-2.md-warn path{stroke:rgb(216,67,21)}md-progress-linear.md-default-theme.md-hue-2.md-warn .md-container, md-progress-linear.md-hue-2.md-warn .md-container{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-2.md-warn .md-bar, md-progress-linear.md-hue-2.md-warn .md-bar{background-color:rgb(216,67,21)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-warn .md-bar1, md-progress-linear.md-hue-2[md-mode=buffer].md-warn .md-bar1{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-warn .md-dashed:before, md-progress-linear.md-hue-2[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(rgb(255,204,188) 0,rgb(255,204,188) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn .md-on, md-radio-button.md-hue-2:not([disabled]).md-warn .md-on,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-on, md-radio-button.md-hue-2:not([disabled]) .md-warn .md-on,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn .md-on, md-radio-group.md-hue-2:not([disabled]).md-warn .md-on,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-on, md-radio-group.md-hue-2:not([disabled]) .md-warn .md-on{background-color:rgba(216,67,21,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-hue-2:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-hue-2:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-hue-2:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-hue-2:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-off, md-radio-group.md-hue-2:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-hue-2:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-hue-2:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-hue-2:not([disabled]) .md-warn .md-checked .md-off{border-color:rgba(216,67,21,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:rgba(216,67,21,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-hue-2:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-hue-2:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn .md-container .md-ripple, md-radio-group.md-hue-2:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-hue-2:not([disabled]) .md-warn .md-container .md-ripple{color:rgb(244,81,30)}md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty) .md-checked.md-warn .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty).md-warn .md-checked .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:rgba(216,67,21,0.26)}md-input-container md-select.md-default-theme.md-hue-2 .md-select-value span:first-child:after, md-input-container md-select.md-hue-2 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-2 .md-select-value, md-input-container.md-input-invalid md-select.md-hue-2 .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-2 .md-select-value span:first-child:after, md-select.md-hue-2 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-select.md-default-theme.md-hue-2.ng-invalid.ng-touched .md-select-value, md-select.md-hue-2.ng-invalid.ng-touched .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-2:not([disabled]):focus.md-warn .md-select-value, md-select.md-hue-2:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-focus-ring, md-slider.md-hue-2.md-warn .md-focus-ring{background-color:rgba(255,171,145,0.38)}md-slider.md-default-theme.md-hue-2.md-warn .md-track.md-track-fill, md-slider.md-hue-2.md-warn .md-track.md-track-fill{background-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-thumb:after, md-slider.md-hue-2.md-warn .md-thumb:after{border-color:rgb(216,67,21);background-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-sign, md-slider.md-hue-2.md-warn .md-sign{background-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-sign:after, md-slider.md-hue-2.md-warn .md-sign:after{border-top-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn[md-vertical] .md-sign:after, md-slider.md-hue-2.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-thumb-text, md-slider.md-hue-2.md-warn .md-thumb-text{color:rgb(255,255,255)}.md-subheader.md-default-theme.md-hue-2.md-warn, .md-subheader.md-hue-2.md-warn{color:rgb(216,67,21)}md-switch.md-default-theme.md-hue-2.md-checked.md-warn .md-ink-ripple, md-switch.md-hue-2.md-checked.md-warn .md-ink-ripple{color:rgb(216,67,21)}md-switch.md-default-theme.md-hue-2.md-checked.md-warn .md-thumb, md-switch.md-hue-2.md-checked.md-warn .md-thumb{background-color:rgb(216,67,21)}md-switch.md-default-theme.md-hue-2.md-checked.md-warn .md-bar, md-switch.md-hue-2.md-checked.md-warn .md-bar{background-color:rgba(216,67,21,0.5)}md-switch.md-default-theme.md-hue-2.md-checked.md-warn.md-focused .md-thumb:before, md-switch.md-hue-2.md-checked.md-warn.md-focused .md-thumb:before{background-color:rgba(216,67,21,0.26)}md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper, md-tabs.md-hue-2.md-warn>md-tabs-wrapper{background-color:rgb(216,67,21)}md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgb(255,255,255)}md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper{background-color:rgb(216,67,21)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgb(255,255,255)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toast.md-default-theme.md-hue-2 .md-toast-content .md-button.md-highlight.md-warn, md-toast.md-hue-2 .md-toast-content .md-button.md-highlight.md-warn{color:rgb(216,67,21)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-warn, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-warn{background-color:rgb(216,67,21);color:rgb(255,255,255)} -.md-button.md-default-theme.md-hue-3.md-warn, .md-button.md-hue-3.md-warn{color:rgb(255,158,128)}.md-button.md-default-theme.md-hue-3.md-warn.md-fab, .md-button.md-hue-3.md-warn.md-fab,.md-button.md-default-theme.md-hue-3.md-warn.md-raised, .md-button.md-hue-3.md-warn.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(255,158,128)}.md-button.md-default-theme.md-hue-3.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-hue-3.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-3.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-hue-3.md-warn.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-hue-3.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-warn.md-fab:not([disabled]):hover, .md-button.md-hue-3.md-warn.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-3.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-hue-3.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-warn.md-raised:not([disabled]):hover, .md-button.md-hue-3.md-warn.md-raised:not([disabled]):hover{background-color:rgb(244,81,30)}.md-button.md-default-theme.md-hue-3.md-warn:not([disabled]) md-icon, .md-button.md-hue-3.md-warn:not([disabled]) md-icon{color:rgb(255,158,128)}._md a.md-default-theme.md-hue-3:not(.md-button).md-warn, ._md a.md-hue-3:not(.md-button).md-warn{color:rgb(255,158,128)}._md a.md-default-theme.md-hue-3:not(.md-button).md-warn:hover, ._md a.md-hue-3:not(.md-button).md-warn:hover{color:rgb(230,74,25)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn .md-ripple, md-checkbox.md-hue-3:not([disabled]).md-warn .md-ripple{color:rgb(244,81,30)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn .md-ink-ripple, md-checkbox.md-hue-3:not([disabled]).md-warn .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple, md-checkbox.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple{color:rgba(255,158,128,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn:not(.md-checked) .md-icon, md-checkbox.md-hue-3:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-icon, md-checkbox.md-hue-3:not([disabled]).md-warn.md-checked .md-icon{background-color:rgba(255,158,128,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before, md-checkbox.md-hue-3:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:rgba(255,158,128,0.26)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-icon:after, md-checkbox.md-hue-3:not([disabled]).md-warn.md-checked .md-icon:after{border-color:rgb(238,238,238)}.md-default-theme.md-hue-3 .md-datepicker-input-container.md-datepicker-invalid, .md-hue-3 .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-default-theme.md-hue-3 .md-datepicker-input-container.md-datepicker-focused, .md-warn .md-hue-3 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(221,44,0)}.md-default-theme.md-hue-3 .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-hue-3 .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-default-theme.md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon, .md-warn .md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(221,44,0)}md-icon.md-default-theme.md-hue-3.md-warn, md-icon.md-hue-3.md-warn{color:rgb(255,158,128)}md-input-container.md-default-theme.md-hue-3 label.md-required:after, md-input-container.md-hue-3 label.md-required:after{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3 .md-input-message-animation, md-input-container.md-hue-3 .md-input-message-animation,md-input-container.md-default-theme.md-hue-3 .md-input-messages-animation, md-input-container.md-hue-3 .md-input-messages-animation{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn .md-input, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn label, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn md-icon, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3.md-input-invalid .md-input, md-input-container.md-hue-3.md-input-invalid .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3.md-input-invalid .md-char-counter, md-input-container.md-hue-3.md-input-invalid .md-char-counter,md-input-container.md-default-theme.md-hue-3.md-input-invalid .md-input-message-animation, md-input-container.md-hue-3.md-input-invalid .md-input-message-animation,md-input-container.md-default-theme.md-hue-3.md-input-invalid label, md-input-container.md-hue-3.md-input-invalid label{color:rgb(221,44,0)}md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar, md-nav-bar.md-hue-3.md-warn>.md-nav-bar{background-color:rgb(255,158,128)}md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar{background-color:rgb(255,158,128)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-progress-circular.md-default-theme.md-hue-3.md-warn path, md-progress-circular.md-hue-3.md-warn path{stroke:rgb(255,158,128)}md-progress-linear.md-default-theme.md-hue-3.md-warn .md-container, md-progress-linear.md-hue-3.md-warn .md-container{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-3.md-warn .md-bar, md-progress-linear.md-hue-3.md-warn .md-bar{background-color:rgb(255,158,128)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-warn .md-bar1, md-progress-linear.md-hue-3[md-mode=buffer].md-warn .md-bar1{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-warn .md-dashed:before, md-progress-linear.md-hue-3[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(rgb(255,204,188) 0,rgb(255,204,188) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn .md-on, md-radio-button.md-hue-3:not([disabled]).md-warn .md-on,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-on, md-radio-button.md-hue-3:not([disabled]) .md-warn .md-on,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn .md-on, md-radio-group.md-hue-3:not([disabled]).md-warn .md-on,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-on, md-radio-group.md-hue-3:not([disabled]) .md-warn .md-on{background-color:rgba(255,158,128,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-hue-3:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-hue-3:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-hue-3:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-hue-3:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-off, md-radio-group.md-hue-3:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-hue-3:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-hue-3:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-hue-3:not([disabled]) .md-warn .md-checked .md-off{border-color:rgba(255,158,128,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:rgba(255,158,128,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-hue-3:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-hue-3:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn .md-container .md-ripple, md-radio-group.md-hue-3:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-hue-3:not([disabled]) .md-warn .md-container .md-ripple{color:rgb(244,81,30)}md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty) .md-checked.md-warn .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty).md-warn .md-checked .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:rgba(255,158,128,0.26)}md-input-container md-select.md-default-theme.md-hue-3 .md-select-value span:first-child:after, md-input-container md-select.md-hue-3 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-3 .md-select-value, md-input-container.md-input-invalid md-select.md-hue-3 .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-3 .md-select-value span:first-child:after, md-select.md-hue-3 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-select.md-default-theme.md-hue-3.ng-invalid.ng-touched .md-select-value, md-select.md-hue-3.ng-invalid.ng-touched .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-3:not([disabled]):focus.md-warn .md-select-value, md-select.md-hue-3:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-focus-ring, md-slider.md-hue-3.md-warn .md-focus-ring{background-color:rgba(255,171,145,0.38)}md-slider.md-default-theme.md-hue-3.md-warn .md-track.md-track-fill, md-slider.md-hue-3.md-warn .md-track.md-track-fill{background-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-thumb:after, md-slider.md-hue-3.md-warn .md-thumb:after{border-color:rgb(255,158,128);background-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-sign, md-slider.md-hue-3.md-warn .md-sign{background-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-sign:after, md-slider.md-hue-3.md-warn .md-sign:after{border-top-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn[md-vertical] .md-sign:after, md-slider.md-hue-3.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-thumb-text, md-slider.md-hue-3.md-warn .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-3.md-warn, .md-subheader.md-hue-3.md-warn{color:rgb(255,158,128)}md-switch.md-default-theme.md-hue-3.md-checked.md-warn .md-ink-ripple, md-switch.md-hue-3.md-checked.md-warn .md-ink-ripple{color:rgb(255,158,128)}md-switch.md-default-theme.md-hue-3.md-checked.md-warn .md-thumb, md-switch.md-hue-3.md-checked.md-warn .md-thumb{background-color:rgb(255,158,128)}md-switch.md-default-theme.md-hue-3.md-checked.md-warn .md-bar, md-switch.md-hue-3.md-checked.md-warn .md-bar{background-color:rgba(255,158,128,0.5)}md-switch.md-default-theme.md-hue-3.md-checked.md-warn.md-focused .md-thumb:before, md-switch.md-hue-3.md-checked.md-warn.md-focused .md-thumb:before{background-color:rgba(255,158,128,0.26)}md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper, md-tabs.md-hue-3.md-warn>md-tabs-wrapper{background-color:rgb(255,158,128)}md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper{background-color:rgb(255,158,128)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toast.md-default-theme.md-hue-3 .md-toast-content .md-button.md-highlight.md-warn, md-toast.md-hue-3 .md-toast-content .md-button.md-highlight.md-warn{color:rgb(255,158,128)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-warn, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-warn{background-color:rgb(255,158,128);color:rgba(0,0,0,0.87)} -md-autocomplete.md-default-theme, md-autocomplete{background:rgb(245,245,245)}md-autocomplete.md-default-theme[disabled]:not([md-floating-label]), md-autocomplete[disabled]:not([md-floating-label]){background:rgb(238,238,238)}md-autocomplete.md-default-theme button md-icon path, md-autocomplete button md-icon path{fill:rgb(117,117,117)}md-autocomplete.md-default-theme button:after, md-autocomplete button:after{background:rgba(117,117,117,0.3)}.md-autocomplete-suggestions-container.md-default-theme, .md-autocomplete-suggestions-container{background:rgb(245,245,245)}.md-autocomplete-suggestions-container.md-default-theme li.selected, .md-autocomplete-suggestions-container li.selected,.md-autocomplete-suggestions-container.md-default-theme li:hover, .md-autocomplete-suggestions-container li:hover{background:rgba(158,158,158,0.18)}md-backdrop{background-color:rgba(33,33,33,0.0)}md-backdrop.md-opaque.md-default-theme, md-backdrop.md-opaque{background-color:rgba(33,33,33,1.0)}md-bottom-sheet.md-default-theme, md-bottom-sheet{background-color:rgb(250,250,250);border-top-color:rgb(224,224,224)}md-bottom-sheet.md-default-theme .md-subheader, md-bottom-sheet .md-subheader{background-color:rgb(250,250,250);color:rgba(0,0,0,0.87)}.md-button.md-default-theme:not([disabled]).md-focused, .md-button:not([disabled]).md-focused,.md-button.md-default-theme:not([disabled]):hover, .md-button:not([disabled]):hover{background-color:rgba(158,158,158,0.2)}.md-button.md-default-theme:not([disabled]).md-icon-button:hover, .md-button:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-default-theme.md-raised, .md-button.md-raised{color:rgb(33,33,33);background-color:rgb(250,250,250)}.md-button.md-default-theme.md-raised:not([disabled]) md-icon, .md-button.md-raised:not([disabled]) md-icon{color:rgb(33,33,33)}.md-button.md-default-theme.md-raised:not([disabled]):hover, .md-button.md-raised:not([disabled]):hover{background-color:rgb(250,250,250)}.md-button.md-default-theme.md-raised:not([disabled]).md-focused, .md-button.md-raised:not([disabled]).md-focused{background-color:rgb(238,238,238)}.md-button.md-default-theme.md-fab[disabled], .md-button.md-fab[disabled],.md-button.md-default-theme.md-raised[disabled], .md-button.md-raised[disabled]{background-color:rgba(0,0,0,0.12)}.md-button.md-default-theme[disabled], .md-button[disabled]{background-color:transparent}md-card.md-default-theme, md-card{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245);border-radius:2px}md-card.md-default-theme md-card-header md-card-avatar md-icon, md-card md-card-header md-card-avatar md-icon{color:rgb(250,250,250);background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-checked .md-ripple, md-checkbox.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme[disabled].md-checked .md-icon, md-checkbox[disabled].md-checked .md-icon{background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme[disabled].md-checked .md-icon:after, md-checkbox[disabled].md-checked .md-icon:after{border-color:rgb(238,238,238)}md-chips.md-default-theme md-chip, md-chips md-chip{background:rgb(224,224,224);color:rgb(66,66,66)}md-chips.md-default-theme md-chip md-icon, md-chips md-chip md-icon{color:rgb(97,97,97)}md-chips.md-default-theme md-chip._md-chip-editing, md-chips md-chip._md-chip-editing{background:transparent;color:rgb(66,66,66)}md-chips.md-default-theme md-chip-remove .md-button md-icon path, md-chips md-chip-remove .md-button md-icon path{fill:rgb(158,158,158)}.md-contact-suggestion span.md-contact-email{color:rgb(189,189,189)}md-content.md-default-theme, md-content{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)}.md-default-theme .md-calendar, .md-calendar{background:rgb(245,245,245);color:rgba(0,0,0,0.87)}.md-default-theme .md-calendar tr:last-child td, .md-calendar tr:last-child td{border-bottom-color:rgb(238,238,238)}.md-default-theme .md-calendar-day-header, .md-calendar-day-header{background:rgba(158,158,158,0.32);color:rgba(0,0,0,0.87)}.md-calendar-date.md-focus .md-default-theme .md-calendar-date-selection-indicator, .md-calendar-date.md-focus .md-calendar-date-selection-indicator,.md-default-theme .md-calendar-date-selection-indicator:hover, .md-calendar-date-selection-indicator:hover{background:rgba(158,158,158,0.32)}.md-default-theme .md-datepicker-calendar-pane, .md-datepicker-calendar-pane{border-color:rgb(245,245,245)}.md-default-theme .md-datepicker-calendar, .md-datepicker-calendar{background:rgb(245,245,245)}.md-default-theme .md-datepicker-input-mask-opaque, .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px rgb(245,245,245)}.md-default-theme .md-datepicker-open .md-datepicker-input-container, .md-datepicker-open .md-datepicker-input-container{background:rgb(245,245,245)}md-dialog.md-default-theme, md-dialog{border-radius:4px;background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}[disabled] md-input-container.md-default-theme .md-input, [disabled] md-input-container .md-input,md-input-container.md-default-theme .md-input[disabled], md-input-container .md-input[disabled]{border-bottom-color:transparent;color:rgba(0,0,0,0.38);background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-list.md-default-theme .md-proxy-focus.md-focused div.md-no-style, md-list .md-proxy-focus.md-focused div.md-no-style{background-color:rgb(245,245,245)}md-list.md-default-theme md-list-item .md-avatar-icon, md-list md-list-item .md-avatar-icon{background-color:rgba(0,0,0,0.38);color:rgb(250,250,250)}md-menu-content.md-default-theme, md-menu-content{background-color:rgb(245,245,245)}md-menu-content.md-default-theme md-menu-divider, md-menu-content md-menu-divider{background-color:rgba(0,0,0,0.12)}md-menu-bar.md-default-theme md-menu.md-open>button, md-menu-bar md-menu.md-open>button,md-menu-bar.md-default-theme md-menu>button:focus, md-menu-bar md-menu>button:focus{outline:none;background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-open:not(.md-keyboard-mode) md-menu:hover>button, md-menu-bar.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:focus, md-menu-bar:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-default-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:hover, md-menu-bar:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-default-theme .md-menu.md-open>.md-button, md-menu-content .md-menu.md-open>.md-button{background-color:rgba(158,158,158,0.18)}md-toolbar.md-default-theme.md-menu-toolbar, md-toolbar.md-menu-toolbar{background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme .md-nav-bar, md-nav-bar .md-nav-bar{background-color:transparent;border-color:rgba(0,0,0,0.12)}._md-panel-backdrop.md-default-theme, ._md-panel-backdrop{background-color:rgba(33,33,33,1.0)}md-select.md-default-theme[disabled] .md-select-value, md-select[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-select-menu.md-default-theme md-content, md-select-menu md-content{background-color:rgb(245,245,245)}md-select-menu.md-default-theme md-content md-option:not([disabled]):focus, md-select-menu md-content md-option:not([disabled]):focus,md-select-menu.md-default-theme md-content md-option:not([disabled]):hover, md-select-menu md-content md-option:not([disabled]):hover{background-color:rgba(158,158,158,0.18)}.md-checkbox-enabled.md-default-theme[selected] .md-ripple, .md-checkbox-enabled[selected] .md-ripple{color:rgb(117,117,117)}md-sidenav.md-default-theme, md-sidenav,md-sidenav.md-default-theme md-content, md-sidenav md-content{background-color:rgb(245,245,245)}md-slider.md-default-theme .md-track, md-slider .md-track{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme .md-track-ticks, md-slider .md-track-ticks{color:rgba(0,0,0,0.87)}md-slider.md-default-theme .md-disabled-thumb, md-slider .md-disabled-thumb{border-color:rgb(250,250,250);background-color:rgb(250,250,250)}md-slider.md-default-theme.md-min .md-thumb:after, md-slider.md-min .md-thumb:after{background-color:rgb(250,250,250);border-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-min .md-focus-ring, md-slider.md-min .md-focus-ring{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-min[md-discrete] .md-thumb:after, md-slider.md-min[md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.87);border-color:transparent}md-slider.md-default-theme.md-min[md-discrete] .md-sign, md-slider.md-min[md-discrete] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-min[md-discrete] .md-sign:after, md-slider.md-min[md-discrete] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-min[md-discrete][md-vertical] .md-sign:after, md-slider.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme[disabled]:not(.md-min) .md-thumb:after, md-slider[disabled]:not(.md-min) .md-thumb:after,md-slider.md-default-theme[disabled][md-discrete] .md-thumb:after, md-slider[disabled][md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.38);border-color:transparent}md-slider.md-default-theme[disabled][readonly] .md-sign, md-slider[disabled][readonly] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme[disabled][readonly] .md-sign:after, md-slider[disabled][readonly] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme[disabled][readonly][md-vertical] .md-sign:after, md-slider[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme[disabled][readonly] .md-disabled-thumb, md-slider[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}.md-subheader.md-default-theme, .md-subheader{color:rgba(0,0,0,0.54);background-color:rgb(250,250,250)}md-switch.md-default-theme .md-ink-ripple, md-switch .md-ink-ripple{color:rgb(158,158,158)}md-switch.md-default-theme .md-thumb, md-switch .md-thumb{background-color:rgb(250,250,250)}md-switch.md-default-theme .md-bar, md-switch .md-bar{background-color:rgb(158,158,158)}md-switch.md-default-theme[disabled] .md-thumb, md-switch[disabled] .md-thumb{background-color:rgb(189,189,189)}md-switch.md-default-theme[disabled] .md-bar, md-switch[disabled] .md-bar{background-color:rgba(0,0,0,0.12)}md-tabs.md-default-theme md-tabs-wrapper, md-tabs md-tabs-wrapper{background-color:transparent;border-color:rgba(0,0,0,0.12)}md-toast.md-default-theme .md-toast-content, md-toast .md-toast-content{background-color:#323232;color:rgb(250,250,250)}md-toast.md-default-theme .md-toast-content .md-button, md-toast .md-toast-content .md-button{color:rgb(250,250,250)}.md-panel.md-tooltip.md-default-theme, .md-panel.md-tooltip{color:rgba(255,255,255,0.87);background-color:rgb(97,97,97)}body.md-default-theme, body,html.md-default-theme, html{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)} -md-autocomplete.md-default-theme.md-hue-1, md-autocomplete.md-hue-1{background:rgb(245,245,245)}md-autocomplete.md-default-theme.md-hue-1[disabled]:not([md-floating-label]), md-autocomplete.md-hue-1[disabled]:not([md-floating-label]){background:rgb(238,238,238)}md-autocomplete.md-default-theme.md-hue-1 button md-icon path, md-autocomplete.md-hue-1 button md-icon path{fill:rgb(117,117,117)}md-autocomplete.md-default-theme.md-hue-1 button:after, md-autocomplete.md-hue-1 button:after{background:rgba(117,117,117,0.3)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-1, .md-autocomplete-suggestions-container.md-hue-1{background:rgb(245,245,245)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-1 li.selected, .md-autocomplete-suggestions-container.md-hue-1 li.selected,.md-autocomplete-suggestions-container.md-default-theme.md-hue-1 li:hover, .md-autocomplete-suggestions-container.md-hue-1 li:hover{background:rgba(158,158,158,0.18)}md-backdrop{background-color:rgba(33,33,33,0.0)}md-backdrop.md-opaque.md-default-theme.md-hue-1, md-backdrop.md-opaque.md-hue-1{background-color:rgba(33,33,33,1.0)}md-bottom-sheet.md-default-theme.md-hue-1, md-bottom-sheet.md-hue-1{background-color:rgb(250,250,250);border-top-color:rgb(224,224,224)}md-bottom-sheet.md-default-theme.md-hue-1 .md-subheader, md-bottom-sheet.md-hue-1 .md-subheader{background-color:rgb(250,250,250);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1:not([disabled]).md-focused, .md-button.md-hue-1:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1:not([disabled]):hover, .md-button.md-hue-1:not([disabled]):hover{background-color:rgba(158,158,158,0.2)}.md-button.md-default-theme.md-hue-1:not([disabled]).md-icon-button:hover, .md-button.md-hue-1:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-default-theme.md-hue-1.md-raised, .md-button.md-hue-1.md-raised{color:rgb(33,33,33);background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-1.md-raised:not([disabled]) md-icon, .md-button.md-hue-1.md-raised:not([disabled]) md-icon{color:rgb(33,33,33)}.md-button.md-default-theme.md-hue-1.md-raised:not([disabled]):hover, .md-button.md-hue-1.md-raised:not([disabled]):hover{background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-1.md-raised:not([disabled]).md-focused, .md-button.md-hue-1.md-raised:not([disabled]).md-focused{background-color:rgb(238,238,238)}.md-button.md-default-theme.md-hue-1.md-fab[disabled], .md-button.md-hue-1.md-fab[disabled],.md-button.md-default-theme.md-hue-1.md-raised[disabled], .md-button.md-hue-1.md-raised[disabled]{background-color:rgba(0,0,0,0.12)}.md-button.md-default-theme.md-hue-1[disabled], .md-button.md-hue-1[disabled]{background-color:transparent}md-card.md-default-theme.md-hue-1, md-card.md-hue-1{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245);border-radius:2px}md-card.md-default-theme.md-hue-1 md-card-header md-card-avatar md-icon, md-card.md-hue-1 md-card-header md-card-avatar md-icon{color:rgb(245,245,245);background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1.md-checked .md-ripple, md-checkbox.md-hue-1.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-1[disabled].md-checked .md-icon, md-checkbox.md-hue-1[disabled].md-checked .md-icon{background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1[disabled].md-checked .md-icon:after, md-checkbox.md-hue-1[disabled].md-checked .md-icon:after{border-color:rgb(238,238,238)}md-chips.md-default-theme.md-hue-1 md-chip, md-chips.md-hue-1 md-chip{background:rgb(224,224,224);color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-1 md-chip md-icon, md-chips.md-hue-1 md-chip md-icon{color:rgb(97,97,97)}md-chips.md-default-theme.md-hue-1 md-chip._md-chip-editing, md-chips.md-hue-1 md-chip._md-chip-editing{background:transparent;color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-1 md-chip-remove .md-button md-icon path, md-chips.md-hue-1 md-chip-remove .md-button md-icon path{fill:rgb(158,158,158)}.md-contact-suggestion span.md-contact-email{color:rgb(189,189,189)}md-content.md-default-theme.md-hue-1, md-content.md-hue-1{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)}.md-default-theme.md-hue-1 .md-calendar, .md-hue-1 .md-calendar{background:rgb(245,245,245);color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-1 .md-calendar tr:last-child td, .md-hue-1 .md-calendar tr:last-child td{border-bottom-color:rgb(238,238,238)}.md-default-theme.md-hue-1 .md-calendar-day-header, .md-hue-1 .md-calendar-day-header{background:rgba(158,158,158,0.32);color:rgba(0,0,0,0.87)}.md-calendar-date.md-focus .md-default-theme.md-hue-1 .md-calendar-date-selection-indicator, .md-calendar-date.md-focus .md-hue-1 .md-calendar-date-selection-indicator,.md-default-theme.md-hue-1 .md-calendar-date-selection-indicator:hover, .md-hue-1 .md-calendar-date-selection-indicator:hover{background:rgba(158,158,158,0.32)}.md-default-theme.md-hue-1 .md-datepicker-calendar-pane, .md-hue-1 .md-datepicker-calendar-pane{border-color:rgb(245,245,245)}.md-default-theme.md-hue-1 .md-datepicker-calendar, .md-hue-1 .md-datepicker-calendar{background:rgb(245,245,245)}.md-default-theme.md-hue-1 .md-datepicker-input-mask-opaque, .md-hue-1 .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px rgb(245,245,245)}.md-default-theme.md-hue-1 .md-datepicker-open .md-datepicker-input-container, .md-hue-1 .md-datepicker-open .md-datepicker-input-container{background:rgb(245,245,245)}md-dialog.md-default-theme.md-hue-1, md-dialog.md-hue-1{border-radius:4px;background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}[disabled] md-input-container.md-default-theme.md-hue-1 .md-input, [disabled] md-input-container.md-hue-1 .md-input,md-input-container.md-default-theme.md-hue-1 .md-input[disabled], md-input-container.md-hue-1 .md-input[disabled]{border-bottom-color:transparent;color:rgba(0,0,0,0.38);background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-list.md-default-theme.md-hue-1 .md-proxy-focus.md-focused div.md-no-style, md-list.md-hue-1 .md-proxy-focus.md-focused div.md-no-style{background-color:rgb(245,245,245)}md-list.md-default-theme.md-hue-1 md-list-item .md-avatar-icon, md-list.md-hue-1 md-list-item .md-avatar-icon{background-color:rgba(0,0,0,0.38);color:rgb(245,245,245)}md-menu-content.md-default-theme.md-hue-1, md-menu-content.md-hue-1{background-color:rgb(245,245,245)}md-menu-content.md-default-theme.md-hue-1 md-menu-divider, md-menu-content.md-hue-1 md-menu-divider{background-color:rgba(0,0,0,0.12)}md-menu-bar.md-default-theme.md-hue-1 md-menu.md-open>button, md-menu-bar.md-hue-1 md-menu.md-open>button,md-menu-bar.md-default-theme.md-hue-1 md-menu>button:focus, md-menu-bar.md-hue-1 md-menu>button:focus{outline:none;background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-1.md-open:not(.md-keyboard-mode) md-menu:hover>button, md-menu-bar.md-hue-1.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-1:not(.md-keyboard-mode):not(.md-open) md-menu button:focus, md-menu-bar.md-hue-1:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-default-theme.md-hue-1:not(.md-keyboard-mode):not(.md-open) md-menu button:hover, md-menu-bar.md-hue-1:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-default-theme.md-hue-1 .md-menu.md-open>.md-button, md-menu-content.md-hue-1 .md-menu.md-open>.md-button{background-color:rgba(158,158,158,0.18)}md-toolbar.md-default-theme.md-hue-1.md-menu-toolbar, md-toolbar.md-hue-1.md-menu-toolbar{background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1 .md-nav-bar, md-nav-bar.md-hue-1 .md-nav-bar{background-color:transparent;border-color:rgba(0,0,0,0.12)}._md-panel-backdrop.md-default-theme.md-hue-1, ._md-panel-backdrop.md-hue-1{background-color:rgba(33,33,33,1.0)}md-select.md-default-theme.md-hue-1[disabled] .md-select-value, md-select.md-hue-1[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-select-menu.md-default-theme.md-hue-1 md-content, md-select-menu.md-hue-1 md-content{background-color:rgb(245,245,245)}md-select-menu.md-default-theme.md-hue-1 md-content md-option:not([disabled]):focus, md-select-menu.md-hue-1 md-content md-option:not([disabled]):focus,md-select-menu.md-default-theme.md-hue-1 md-content md-option:not([disabled]):hover, md-select-menu.md-hue-1 md-content md-option:not([disabled]):hover{background-color:rgba(158,158,158,0.18)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected] .md-ripple, .md-checkbox-enabled.md-hue-1[selected] .md-ripple{color:rgb(117,117,117)}md-sidenav.md-default-theme.md-hue-1, md-sidenav.md-hue-1,md-sidenav.md-default-theme.md-hue-1 md-content, md-sidenav.md-hue-1 md-content{background-color:rgb(245,245,245)}md-slider.md-default-theme.md-hue-1 .md-track, md-slider.md-hue-1 .md-track{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-1 .md-track-ticks, md-slider.md-hue-1 .md-track-ticks{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-1 .md-disabled-thumb, md-slider.md-hue-1 .md-disabled-thumb{border-color:rgb(245,245,245);background-color:rgb(245,245,245)}md-slider.md-default-theme.md-hue-1.md-min .md-thumb:after, md-slider.md-hue-1.md-min .md-thumb:after{background-color:rgb(245,245,245);border-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-1.md-min .md-focus-ring, md-slider.md-hue-1.md-min .md-focus-ring{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-1.md-min[md-discrete] .md-thumb:after, md-slider.md-hue-1.md-min[md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.87);border-color:transparent}md-slider.md-default-theme.md-hue-1.md-min[md-discrete] .md-sign, md-slider.md-hue-1.md-min[md-discrete] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1.md-min[md-discrete] .md-sign:after, md-slider.md-hue-1.md-min[md-discrete] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1.md-min[md-discrete][md-vertical] .md-sign:after, md-slider.md-hue-1.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1[disabled]:not(.md-min) .md-thumb:after, md-slider.md-hue-1[disabled]:not(.md-min) .md-thumb:after,md-slider.md-default-theme.md-hue-1[disabled][md-discrete] .md-thumb:after, md-slider.md-hue-1[disabled][md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.38);border-color:transparent}md-slider.md-default-theme.md-hue-1[disabled][readonly] .md-sign, md-slider.md-hue-1[disabled][readonly] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1[disabled][readonly] .md-sign:after, md-slider.md-hue-1[disabled][readonly] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1[disabled][readonly][md-vertical] .md-sign:after, md-slider.md-hue-1[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1[disabled][readonly] .md-disabled-thumb, md-slider.md-hue-1[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}.md-subheader.md-default-theme.md-hue-1, .md-subheader.md-hue-1{color:rgba(0,0,0,0.54);background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-1 .md-ink-ripple, md-switch.md-hue-1 .md-ink-ripple{color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-1 .md-thumb, md-switch.md-hue-1 .md-thumb{background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-1 .md-bar, md-switch.md-hue-1 .md-bar{background-color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-1[disabled] .md-thumb, md-switch.md-hue-1[disabled] .md-thumb{background-color:rgb(189,189,189)}md-switch.md-default-theme.md-hue-1[disabled] .md-bar, md-switch.md-hue-1[disabled] .md-bar{background-color:rgba(0,0,0,0.12)}md-tabs.md-default-theme.md-hue-1 md-tabs-wrapper, md-tabs.md-hue-1 md-tabs-wrapper{background-color:transparent;border-color:rgba(0,0,0,0.12)}md-toast.md-default-theme.md-hue-1 .md-toast-content, md-toast.md-hue-1 .md-toast-content{background-color:#323232;color:rgb(250,250,250)}md-toast.md-default-theme.md-hue-1 .md-toast-content .md-button, md-toast.md-hue-1 .md-toast-content .md-button{color:rgb(250,250,250)}.md-panel.md-tooltip.md-default-theme.md-hue-1, .md-panel.md-tooltip.md-hue-1{color:rgba(255,255,255,0.87);background-color:rgb(97,97,97)}body.md-default-theme.md-hue-1, body.md-hue-1,html.md-default-theme.md-hue-1, html.md-hue-1{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245)} -md-autocomplete.md-default-theme.md-hue-2, md-autocomplete.md-hue-2{background:rgb(245,245,245)}md-autocomplete.md-default-theme.md-hue-2[disabled]:not([md-floating-label]), md-autocomplete.md-hue-2[disabled]:not([md-floating-label]){background:rgb(238,238,238)}md-autocomplete.md-default-theme.md-hue-2 button md-icon path, md-autocomplete.md-hue-2 button md-icon path{fill:rgb(117,117,117)}md-autocomplete.md-default-theme.md-hue-2 button:after, md-autocomplete.md-hue-2 button:after{background:rgba(117,117,117,0.3)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-2, .md-autocomplete-suggestions-container.md-hue-2{background:rgb(245,245,245)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-2 li.selected, .md-autocomplete-suggestions-container.md-hue-2 li.selected,.md-autocomplete-suggestions-container.md-default-theme.md-hue-2 li:hover, .md-autocomplete-suggestions-container.md-hue-2 li:hover{background:rgba(158,158,158,0.18)}md-backdrop{background-color:rgba(33,33,33,0.0)}md-backdrop.md-opaque.md-default-theme.md-hue-2, md-backdrop.md-opaque.md-hue-2{background-color:rgba(33,33,33,1.0)}md-bottom-sheet.md-default-theme.md-hue-2, md-bottom-sheet.md-hue-2{background-color:rgb(250,250,250);border-top-color:rgb(224,224,224)}md-bottom-sheet.md-default-theme.md-hue-2 .md-subheader, md-bottom-sheet.md-hue-2 .md-subheader{background-color:rgb(250,250,250);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2:not([disabled]).md-focused, .md-button.md-hue-2:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2:not([disabled]):hover, .md-button.md-hue-2:not([disabled]):hover{background-color:rgba(158,158,158,0.2)}.md-button.md-default-theme.md-hue-2:not([disabled]).md-icon-button:hover, .md-button.md-hue-2:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-default-theme.md-hue-2.md-raised, .md-button.md-hue-2.md-raised{color:rgb(33,33,33);background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-2.md-raised:not([disabled]) md-icon, .md-button.md-hue-2.md-raised:not([disabled]) md-icon{color:rgb(33,33,33)}.md-button.md-default-theme.md-hue-2.md-raised:not([disabled]):hover, .md-button.md-hue-2.md-raised:not([disabled]):hover{background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-2.md-raised:not([disabled]).md-focused, .md-button.md-hue-2.md-raised:not([disabled]).md-focused{background-color:rgb(238,238,238)}.md-button.md-default-theme.md-hue-2.md-fab[disabled], .md-button.md-hue-2.md-fab[disabled],.md-button.md-default-theme.md-hue-2.md-raised[disabled], .md-button.md-hue-2.md-raised[disabled]{background-color:rgba(0,0,0,0.12)}.md-button.md-default-theme.md-hue-2[disabled], .md-button.md-hue-2[disabled]{background-color:transparent}md-card.md-default-theme.md-hue-2, md-card.md-hue-2{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245);border-radius:2px}md-card.md-default-theme.md-hue-2 md-card-header md-card-avatar md-icon, md-card.md-hue-2 md-card-header md-card-avatar md-icon{color:rgb(238,238,238);background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2.md-checked .md-ripple, md-checkbox.md-hue-2.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-2[disabled].md-checked .md-icon, md-checkbox.md-hue-2[disabled].md-checked .md-icon{background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2[disabled].md-checked .md-icon:after, md-checkbox.md-hue-2[disabled].md-checked .md-icon:after{border-color:rgb(238,238,238)}md-chips.md-default-theme.md-hue-2 md-chip, md-chips.md-hue-2 md-chip{background:rgb(224,224,224);color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-2 md-chip md-icon, md-chips.md-hue-2 md-chip md-icon{color:rgb(97,97,97)}md-chips.md-default-theme.md-hue-2 md-chip._md-chip-editing, md-chips.md-hue-2 md-chip._md-chip-editing{background:transparent;color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-2 md-chip-remove .md-button md-icon path, md-chips.md-hue-2 md-chip-remove .md-button md-icon path{fill:rgb(158,158,158)}.md-contact-suggestion span.md-contact-email{color:rgb(189,189,189)}md-content.md-default-theme.md-hue-2, md-content.md-hue-2{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)}.md-default-theme.md-hue-2 .md-calendar, .md-hue-2 .md-calendar{background:rgb(245,245,245);color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-2 .md-calendar tr:last-child td, .md-hue-2 .md-calendar tr:last-child td{border-bottom-color:rgb(238,238,238)}.md-default-theme.md-hue-2 .md-calendar-day-header, .md-hue-2 .md-calendar-day-header{background:rgba(158,158,158,0.32);color:rgba(0,0,0,0.87)}.md-calendar-date.md-focus .md-default-theme.md-hue-2 .md-calendar-date-selection-indicator, .md-calendar-date.md-focus .md-hue-2 .md-calendar-date-selection-indicator,.md-default-theme.md-hue-2 .md-calendar-date-selection-indicator:hover, .md-hue-2 .md-calendar-date-selection-indicator:hover{background:rgba(158,158,158,0.32)}.md-default-theme.md-hue-2 .md-datepicker-calendar-pane, .md-hue-2 .md-datepicker-calendar-pane{border-color:rgb(245,245,245)}.md-default-theme.md-hue-2 .md-datepicker-calendar, .md-hue-2 .md-datepicker-calendar{background:rgb(245,245,245)}.md-default-theme.md-hue-2 .md-datepicker-input-mask-opaque, .md-hue-2 .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px rgb(245,245,245)}.md-default-theme.md-hue-2 .md-datepicker-open .md-datepicker-input-container, .md-hue-2 .md-datepicker-open .md-datepicker-input-container{background:rgb(245,245,245)}md-dialog.md-default-theme.md-hue-2, md-dialog.md-hue-2{border-radius:4px;background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}[disabled] md-input-container.md-default-theme.md-hue-2 .md-input, [disabled] md-input-container.md-hue-2 .md-input,md-input-container.md-default-theme.md-hue-2 .md-input[disabled], md-input-container.md-hue-2 .md-input[disabled]{border-bottom-color:transparent;color:rgba(0,0,0,0.38);background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-list.md-default-theme.md-hue-2 .md-proxy-focus.md-focused div.md-no-style, md-list.md-hue-2 .md-proxy-focus.md-focused div.md-no-style{background-color:rgb(245,245,245)}md-list.md-default-theme.md-hue-2 md-list-item .md-avatar-icon, md-list.md-hue-2 md-list-item .md-avatar-icon{background-color:rgba(0,0,0,0.38);color:rgb(238,238,238)}md-menu-content.md-default-theme.md-hue-2, md-menu-content.md-hue-2{background-color:rgb(245,245,245)}md-menu-content.md-default-theme.md-hue-2 md-menu-divider, md-menu-content.md-hue-2 md-menu-divider{background-color:rgba(0,0,0,0.12)}md-menu-bar.md-default-theme.md-hue-2 md-menu.md-open>button, md-menu-bar.md-hue-2 md-menu.md-open>button,md-menu-bar.md-default-theme.md-hue-2 md-menu>button:focus, md-menu-bar.md-hue-2 md-menu>button:focus{outline:none;background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-2.md-open:not(.md-keyboard-mode) md-menu:hover>button, md-menu-bar.md-hue-2.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-2:not(.md-keyboard-mode):not(.md-open) md-menu button:focus, md-menu-bar.md-hue-2:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-default-theme.md-hue-2:not(.md-keyboard-mode):not(.md-open) md-menu button:hover, md-menu-bar.md-hue-2:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-default-theme.md-hue-2 .md-menu.md-open>.md-button, md-menu-content.md-hue-2 .md-menu.md-open>.md-button{background-color:rgba(158,158,158,0.18)}md-toolbar.md-default-theme.md-hue-2.md-menu-toolbar, md-toolbar.md-hue-2.md-menu-toolbar{background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-2 .md-nav-bar, md-nav-bar.md-hue-2 .md-nav-bar{background-color:transparent;border-color:rgba(0,0,0,0.12)}._md-panel-backdrop.md-default-theme.md-hue-2, ._md-panel-backdrop.md-hue-2{background-color:rgba(33,33,33,1.0)}md-select.md-default-theme.md-hue-2[disabled] .md-select-value, md-select.md-hue-2[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-select-menu.md-default-theme.md-hue-2 md-content, md-select-menu.md-hue-2 md-content{background-color:rgb(245,245,245)}md-select-menu.md-default-theme.md-hue-2 md-content md-option:not([disabled]):focus, md-select-menu.md-hue-2 md-content md-option:not([disabled]):focus,md-select-menu.md-default-theme.md-hue-2 md-content md-option:not([disabled]):hover, md-select-menu.md-hue-2 md-content md-option:not([disabled]):hover{background-color:rgba(158,158,158,0.18)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected] .md-ripple, .md-checkbox-enabled.md-hue-2[selected] .md-ripple{color:rgb(117,117,117)}md-sidenav.md-default-theme.md-hue-2, md-sidenav.md-hue-2,md-sidenav.md-default-theme.md-hue-2 md-content, md-sidenav.md-hue-2 md-content{background-color:rgb(245,245,245)}md-slider.md-default-theme.md-hue-2 .md-track, md-slider.md-hue-2 .md-track{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-2 .md-track-ticks, md-slider.md-hue-2 .md-track-ticks{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-2 .md-disabled-thumb, md-slider.md-hue-2 .md-disabled-thumb{border-color:rgb(238,238,238);background-color:rgb(238,238,238)}md-slider.md-default-theme.md-hue-2.md-min .md-thumb:after, md-slider.md-hue-2.md-min .md-thumb:after{background-color:rgb(238,238,238);border-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-2.md-min .md-focus-ring, md-slider.md-hue-2.md-min .md-focus-ring{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-2.md-min[md-discrete] .md-thumb:after, md-slider.md-hue-2.md-min[md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.87);border-color:transparent}md-slider.md-default-theme.md-hue-2.md-min[md-discrete] .md-sign, md-slider.md-hue-2.md-min[md-discrete] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2.md-min[md-discrete] .md-sign:after, md-slider.md-hue-2.md-min[md-discrete] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2.md-min[md-discrete][md-vertical] .md-sign:after, md-slider.md-hue-2.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2[disabled]:not(.md-min) .md-thumb:after, md-slider.md-hue-2[disabled]:not(.md-min) .md-thumb:after,md-slider.md-default-theme.md-hue-2[disabled][md-discrete] .md-thumb:after, md-slider.md-hue-2[disabled][md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.38);border-color:transparent}md-slider.md-default-theme.md-hue-2[disabled][readonly] .md-sign, md-slider.md-hue-2[disabled][readonly] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2[disabled][readonly] .md-sign:after, md-slider.md-hue-2[disabled][readonly] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2[disabled][readonly][md-vertical] .md-sign:after, md-slider.md-hue-2[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2[disabled][readonly] .md-disabled-thumb, md-slider.md-hue-2[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}.md-subheader.md-default-theme.md-hue-2, .md-subheader.md-hue-2{color:rgba(0,0,0,0.54);background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-2 .md-ink-ripple, md-switch.md-hue-2 .md-ink-ripple{color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-2 .md-thumb, md-switch.md-hue-2 .md-thumb{background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-2 .md-bar, md-switch.md-hue-2 .md-bar{background-color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-2[disabled] .md-thumb, md-switch.md-hue-2[disabled] .md-thumb{background-color:rgb(189,189,189)}md-switch.md-default-theme.md-hue-2[disabled] .md-bar, md-switch.md-hue-2[disabled] .md-bar{background-color:rgba(0,0,0,0.12)}md-tabs.md-default-theme.md-hue-2 md-tabs-wrapper, md-tabs.md-hue-2 md-tabs-wrapper{background-color:transparent;border-color:rgba(0,0,0,0.12)}md-toast.md-default-theme.md-hue-2 .md-toast-content, md-toast.md-hue-2 .md-toast-content{background-color:#323232;color:rgb(250,250,250)}md-toast.md-default-theme.md-hue-2 .md-toast-content .md-button, md-toast.md-hue-2 .md-toast-content .md-button{color:rgb(250,250,250)}.md-panel.md-tooltip.md-default-theme.md-hue-2, .md-panel.md-tooltip.md-hue-2{color:rgba(255,255,255,0.87);background-color:rgb(97,97,97)}body.md-default-theme.md-hue-2, body.md-hue-2,html.md-default-theme.md-hue-2, html.md-hue-2{color:rgba(0,0,0,0.87);background-color:rgb(238,238,238)} -md-autocomplete.md-default-theme.md-hue-3, md-autocomplete.md-hue-3{background:rgb(245,245,245)}md-autocomplete.md-default-theme.md-hue-3[disabled]:not([md-floating-label]), md-autocomplete.md-hue-3[disabled]:not([md-floating-label]){background:rgb(238,238,238)}md-autocomplete.md-default-theme.md-hue-3 button md-icon path, md-autocomplete.md-hue-3 button md-icon path{fill:rgb(117,117,117)}md-autocomplete.md-default-theme.md-hue-3 button:after, md-autocomplete.md-hue-3 button:after{background:rgba(117,117,117,0.3)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-3, .md-autocomplete-suggestions-container.md-hue-3{background:rgb(245,245,245)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-3 li.selected, .md-autocomplete-suggestions-container.md-hue-3 li.selected,.md-autocomplete-suggestions-container.md-default-theme.md-hue-3 li:hover, .md-autocomplete-suggestions-container.md-hue-3 li:hover{background:rgba(158,158,158,0.18)}md-backdrop{background-color:rgba(33,33,33,0.0)}md-backdrop.md-opaque.md-default-theme.md-hue-3, md-backdrop.md-opaque.md-hue-3{background-color:rgba(33,33,33,1.0)}md-bottom-sheet.md-default-theme.md-hue-3, md-bottom-sheet.md-hue-3{background-color:rgb(250,250,250);border-top-color:rgb(224,224,224)}md-bottom-sheet.md-default-theme.md-hue-3 .md-subheader, md-bottom-sheet.md-hue-3 .md-subheader{background-color:rgb(250,250,250);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3:not([disabled]).md-focused, .md-button.md-hue-3:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3:not([disabled]):hover, .md-button.md-hue-3:not([disabled]):hover{background-color:rgba(158,158,158,0.2)}.md-button.md-default-theme.md-hue-3:not([disabled]).md-icon-button:hover, .md-button.md-hue-3:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-default-theme.md-hue-3.md-raised, .md-button.md-hue-3.md-raised{color:rgb(33,33,33);background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-3.md-raised:not([disabled]) md-icon, .md-button.md-hue-3.md-raised:not([disabled]) md-icon{color:rgb(33,33,33)}.md-button.md-default-theme.md-hue-3.md-raised:not([disabled]):hover, .md-button.md-hue-3.md-raised:not([disabled]):hover{background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-3.md-raised:not([disabled]).md-focused, .md-button.md-hue-3.md-raised:not([disabled]).md-focused{background-color:rgb(238,238,238)}.md-button.md-default-theme.md-hue-3.md-fab[disabled], .md-button.md-hue-3.md-fab[disabled],.md-button.md-default-theme.md-hue-3.md-raised[disabled], .md-button.md-hue-3.md-raised[disabled]{background-color:rgba(0,0,0,0.12)}.md-button.md-default-theme.md-hue-3[disabled], .md-button.md-hue-3[disabled]{background-color:transparent}md-card.md-default-theme.md-hue-3, md-card.md-hue-3{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245);border-radius:2px}md-card.md-default-theme.md-hue-3 md-card-header md-card-avatar md-icon, md-card.md-hue-3 md-card-header md-card-avatar md-icon{color:rgb(224,224,224);background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3.md-checked .md-ripple, md-checkbox.md-hue-3.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-3[disabled].md-checked .md-icon, md-checkbox.md-hue-3[disabled].md-checked .md-icon{background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3[disabled].md-checked .md-icon:after, md-checkbox.md-hue-3[disabled].md-checked .md-icon:after{border-color:rgb(238,238,238)}md-chips.md-default-theme.md-hue-3 md-chip, md-chips.md-hue-3 md-chip{background:rgb(224,224,224);color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-3 md-chip md-icon, md-chips.md-hue-3 md-chip md-icon{color:rgb(97,97,97)}md-chips.md-default-theme.md-hue-3 md-chip._md-chip-editing, md-chips.md-hue-3 md-chip._md-chip-editing{background:transparent;color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-3 md-chip-remove .md-button md-icon path, md-chips.md-hue-3 md-chip-remove .md-button md-icon path{fill:rgb(158,158,158)}.md-contact-suggestion span.md-contact-email{color:rgb(189,189,189)}md-content.md-default-theme.md-hue-3, md-content.md-hue-3{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)}.md-default-theme.md-hue-3 .md-calendar, .md-hue-3 .md-calendar{background:rgb(245,245,245);color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-3 .md-calendar tr:last-child td, .md-hue-3 .md-calendar tr:last-child td{border-bottom-color:rgb(238,238,238)}.md-default-theme.md-hue-3 .md-calendar-day-header, .md-hue-3 .md-calendar-day-header{background:rgba(158,158,158,0.32);color:rgba(0,0,0,0.87)}.md-calendar-date.md-focus .md-default-theme.md-hue-3 .md-calendar-date-selection-indicator, .md-calendar-date.md-focus .md-hue-3 .md-calendar-date-selection-indicator,.md-default-theme.md-hue-3 .md-calendar-date-selection-indicator:hover, .md-hue-3 .md-calendar-date-selection-indicator:hover{background:rgba(158,158,158,0.32)}.md-default-theme.md-hue-3 .md-datepicker-calendar-pane, .md-hue-3 .md-datepicker-calendar-pane{border-color:rgb(245,245,245)}.md-default-theme.md-hue-3 .md-datepicker-calendar, .md-hue-3 .md-datepicker-calendar{background:rgb(245,245,245)}.md-default-theme.md-hue-3 .md-datepicker-input-mask-opaque, .md-hue-3 .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px rgb(245,245,245)}.md-default-theme.md-hue-3 .md-datepicker-open .md-datepicker-input-container, .md-hue-3 .md-datepicker-open .md-datepicker-input-container{background:rgb(245,245,245)}md-dialog.md-default-theme.md-hue-3, md-dialog.md-hue-3{border-radius:4px;background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}[disabled] md-input-container.md-default-theme.md-hue-3 .md-input, [disabled] md-input-container.md-hue-3 .md-input,md-input-container.md-default-theme.md-hue-3 .md-input[disabled], md-input-container.md-hue-3 .md-input[disabled]{border-bottom-color:transparent;color:rgba(0,0,0,0.38);background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-list.md-default-theme.md-hue-3 .md-proxy-focus.md-focused div.md-no-style, md-list.md-hue-3 .md-proxy-focus.md-focused div.md-no-style{background-color:rgb(245,245,245)}md-list.md-default-theme.md-hue-3 md-list-item .md-avatar-icon, md-list.md-hue-3 md-list-item .md-avatar-icon{background-color:rgba(0,0,0,0.38);color:rgb(224,224,224)}md-menu-content.md-default-theme.md-hue-3, md-menu-content.md-hue-3{background-color:rgb(245,245,245)}md-menu-content.md-default-theme.md-hue-3 md-menu-divider, md-menu-content.md-hue-3 md-menu-divider{background-color:rgba(0,0,0,0.12)}md-menu-bar.md-default-theme.md-hue-3 md-menu.md-open>button, md-menu-bar.md-hue-3 md-menu.md-open>button,md-menu-bar.md-default-theme.md-hue-3 md-menu>button:focus, md-menu-bar.md-hue-3 md-menu>button:focus{outline:none;background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-3.md-open:not(.md-keyboard-mode) md-menu:hover>button, md-menu-bar.md-hue-3.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-3:not(.md-keyboard-mode):not(.md-open) md-menu button:focus, md-menu-bar.md-hue-3:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-default-theme.md-hue-3:not(.md-keyboard-mode):not(.md-open) md-menu button:hover, md-menu-bar.md-hue-3:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-default-theme.md-hue-3 .md-menu.md-open>.md-button, md-menu-content.md-hue-3 .md-menu.md-open>.md-button{background-color:rgba(158,158,158,0.18)}md-toolbar.md-default-theme.md-hue-3.md-menu-toolbar, md-toolbar.md-hue-3.md-menu-toolbar{background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3 .md-nav-bar, md-nav-bar.md-hue-3 .md-nav-bar{background-color:transparent;border-color:rgba(0,0,0,0.12)}._md-panel-backdrop.md-default-theme.md-hue-3, ._md-panel-backdrop.md-hue-3{background-color:rgba(33,33,33,1.0)}md-select.md-default-theme.md-hue-3[disabled] .md-select-value, md-select.md-hue-3[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-select-menu.md-default-theme.md-hue-3 md-content, md-select-menu.md-hue-3 md-content{background-color:rgb(245,245,245)}md-select-menu.md-default-theme.md-hue-3 md-content md-option:not([disabled]):focus, md-select-menu.md-hue-3 md-content md-option:not([disabled]):focus,md-select-menu.md-default-theme.md-hue-3 md-content md-option:not([disabled]):hover, md-select-menu.md-hue-3 md-content md-option:not([disabled]):hover{background-color:rgba(158,158,158,0.18)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected] .md-ripple, .md-checkbox-enabled.md-hue-3[selected] .md-ripple{color:rgb(117,117,117)}md-sidenav.md-default-theme.md-hue-3, md-sidenav.md-hue-3,md-sidenav.md-default-theme.md-hue-3 md-content, md-sidenav.md-hue-3 md-content{background-color:rgb(245,245,245)}md-slider.md-default-theme.md-hue-3 .md-track, md-slider.md-hue-3 .md-track{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-3 .md-track-ticks, md-slider.md-hue-3 .md-track-ticks{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-3 .md-disabled-thumb, md-slider.md-hue-3 .md-disabled-thumb{border-color:rgb(224,224,224);background-color:rgb(224,224,224)}md-slider.md-default-theme.md-hue-3.md-min .md-thumb:after, md-slider.md-hue-3.md-min .md-thumb:after{background-color:rgb(224,224,224);border-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-3.md-min .md-focus-ring, md-slider.md-hue-3.md-min .md-focus-ring{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-3.md-min[md-discrete] .md-thumb:after, md-slider.md-hue-3.md-min[md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.87);border-color:transparent}md-slider.md-default-theme.md-hue-3.md-min[md-discrete] .md-sign, md-slider.md-hue-3.md-min[md-discrete] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3.md-min[md-discrete] .md-sign:after, md-slider.md-hue-3.md-min[md-discrete] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3.md-min[md-discrete][md-vertical] .md-sign:after, md-slider.md-hue-3.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3[disabled]:not(.md-min) .md-thumb:after, md-slider.md-hue-3[disabled]:not(.md-min) .md-thumb:after,md-slider.md-default-theme.md-hue-3[disabled][md-discrete] .md-thumb:after, md-slider.md-hue-3[disabled][md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.38);border-color:transparent}md-slider.md-default-theme.md-hue-3[disabled][readonly] .md-sign, md-slider.md-hue-3[disabled][readonly] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3[disabled][readonly] .md-sign:after, md-slider.md-hue-3[disabled][readonly] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3[disabled][readonly][md-vertical] .md-sign:after, md-slider.md-hue-3[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3[disabled][readonly] .md-disabled-thumb, md-slider.md-hue-3[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}.md-subheader.md-default-theme.md-hue-3, .md-subheader.md-hue-3{color:rgba(0,0,0,0.54);background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-3 .md-ink-ripple, md-switch.md-hue-3 .md-ink-ripple{color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-3 .md-thumb, md-switch.md-hue-3 .md-thumb{background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-3 .md-bar, md-switch.md-hue-3 .md-bar{background-color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-3[disabled] .md-thumb, md-switch.md-hue-3[disabled] .md-thumb{background-color:rgb(189,189,189)}md-switch.md-default-theme.md-hue-3[disabled] .md-bar, md-switch.md-hue-3[disabled] .md-bar{background-color:rgba(0,0,0,0.12)}md-tabs.md-default-theme.md-hue-3 md-tabs-wrapper, md-tabs.md-hue-3 md-tabs-wrapper{background-color:transparent;border-color:rgba(0,0,0,0.12)}md-toast.md-default-theme.md-hue-3 .md-toast-content, md-toast.md-hue-3 .md-toast-content{background-color:#323232;color:rgb(250,250,250)}md-toast.md-default-theme.md-hue-3 .md-toast-content .md-button, md-toast.md-hue-3 .md-toast-content .md-button{color:rgb(250,250,250)}.md-panel.md-tooltip.md-default-theme.md-hue-3, .md-panel.md-tooltip.md-hue-3{color:rgba(255,255,255,0.87);background-color:rgb(97,97,97)}body.md-default-theme.md-hue-3, body.md-hue-3,html.md-default-theme.md-hue-3, html.md-hue-3{color:rgba(0,0,0,0.87);background-color:rgb(224,224,224)} \ No newline at end of file diff --git a/data/Dockerfiles/sogo/theme-blue.js b/data/Dockerfiles/sogo/theme-blue.js deleted file mode 100644 index 865443f9..00000000 --- a/data/Dockerfiles/sogo/theme-blue.js +++ /dev/null @@ -1,103 +0,0 @@ -/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ - -(function() { - 'use strict'; - - angular.module('SOGo.Common') - .config(configure) - - /** - * @ngInject - */ - configure.$inject = ['$mdThemingProvider']; - function configure($mdThemingProvider) { - - // Overwrite values to prevent flipping colors on login screen - $mdThemingProvider.definePalette('mailcow-blue', { - '50': 'E3F2FD', - '100': 'BBDEFB', - '200': '90CAF9', - '300': '64B5F6', - '400': '42A5F5', - '500': '2196F3', - '600': '1E88E5', - '700': '1976D2', - '800': '1565C0', - '900': '0D47A1', - '1000': '0D47A1', - 'A100': '82B1FF', - 'A200': '448AFF', - 'A400': '2979ff', - 'A700': '2962ff', - 'contrastDefaultColor': 'dark', - 'contrastLightColors': ['700', '800', '900'], - 'contrastDarkColors': undefined - }); - - $mdThemingProvider.definePalette('sogo-green', { - '50': 'E3F2FD', - '100': 'BBDEFB', - '200': '90CAF9', - '300': '64B5F6', - '400': '42A5F5', - '500': '2196F3', - '600': '1E88E5', - '700': '1976D2', - '800': '1565C0', - '900': '0D47A1', - '1000': '0D47A1', - 'A100': '82B1FF', - 'A200': '448AFF', - 'A400': '2979ff', - 'A700': '2962ff', - 'contrastDefaultColor': 'dark', - 'contrastLightColors': ['700', '800', '900'], - 'contrastDarkColors': undefined - }); - - $mdThemingProvider.definePalette('default', { - '50': 'E3F2FD', - '100': 'BBDEFB', - '200': '90CAF9', - '300': '64B5F6', - '400': '42A5F5', - '500': '2196F3', - '600': '1E88E5', - '700': '1976D2', - '800': '1565C0', - '900': '0D47A1', - '1000': '0D47A1', - 'A100': '82B1FF', - 'A200': '448AFF', - 'A400': '2979ff', - 'A700': '2962ff', - 'contrastDefaultColor': 'dark', - 'contrastLightColors': ['700', '800', '900'], - 'contrastDarkColors': undefined - }); - - $mdThemingProvider.theme('default') - .primaryPalette('mailcow-blue', { - 'default': '700', // top toolbar - 'hue-1': '500', - 'hue-2': '700', // sidebar toolbar - 'hue-3': 'A200' - }) - .accentPalette('mailcow-blue', { - 'default': '800', // fab buttons - 'hue-1': '50', // center list toolbar - 'hue-2': '500', - 'hue-3': 'A700' - }) - .backgroundPalette('grey', { - 'default': '50', // center list background - 'hue-1': '100', - 'hue-2': '200', - 'hue-3': '300' - }); - - $mdThemingProvider.setDefaultTheme('default'); - $mdThemingProvider.generateThemesOnDemand(false); - $mdThemingProvider.alwaysWatchTheme(true); - } -})(); diff --git a/data/Dockerfiles/solr/Dockerfile b/data/Dockerfiles/solr/Dockerfile new file mode 100644 index 00000000..eae8f55a --- /dev/null +++ b/data/Dockerfiles/solr/Dockerfile @@ -0,0 +1,13 @@ +FROM solr:7.7-alpine +USER root +COPY docker-entrypoint.sh / +COPY solr-config-7.7.0.xml / +COPY solr-schema-7.7.0.xml / + + +RUN apk --no-cache add su-exec curl tzdata \ + && chmod +x /docker-entrypoint.sh \ + && sync \ + && bash /docker-entrypoint.sh --bootstrap + +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/data/Dockerfiles/solr/docker-entrypoint.sh b/data/Dockerfiles/solr/docker-entrypoint.sh new file mode 100755 index 00000000..5a33620d --- /dev/null +++ b/data/Dockerfiles/solr/docker-entrypoint.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +if [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo "SKIP_SOLR=y, skipping Solr..." + sleep 365d + exit 0 +fi + +MEM_TOTAL=$(awk '/MemTotal/ {print $2}' /proc/meminfo) + +if [[ "${1}" != "--bootstrap" ]]; then + if [ ${MEM_TOTAL} -lt "2097152" ]; then + echo "System memory less than 2 GB, skipping Solr..." + sleep 365d + exit 0 + fi +fi + +set -e + +# run the optional initdb +. /opt/docker-solr/scripts/run-initdb + +# fixing volume permission +[[ -d /opt/solr/server/solr/dovecot-fts/data ]] && chown -R solr:solr /opt/solr/server/solr/dovecot-fts/data +if [[ "${1}" != "--bootstrap" ]]; then + sed -i '/SOLR_HEAP=/c\SOLR_HEAP="'${SOLR_HEAP:-1024}'m"' /opt/solr/bin/solr.in.sh +else + sed -i '/SOLR_HEAP=/c\SOLR_HEAP="256m"' /opt/solr/bin/solr.in.sh +fi + +if [[ "${1}" == "--bootstrap" ]]; then + echo "Creating initial configuration" + echo "Modifying default config set" + cp /solr-config-7.7.0.xml /opt/solr/server/solr/configsets/_default/conf/solrconfig.xml + cp /solr-schema-7.7.0.xml /opt/solr/server/solr/configsets/_default/conf/schema.xml + rm /opt/solr/server/solr/configsets/_default/conf/managed-schema + + echo "Starting local Solr instance to setup configuration" + su-exec solr start-local-solr + + echo "Creating core \"dovecot-fts\"" + su-exec solr /opt/solr/bin/solr create -c "dovecot-fts" + + # See https://github.com/docker-solr/docker-solr/issues/27 + echo "Checking core" + while ! wget -O - 'http://localhost:8983/solr/admin/cores?action=STATUS' | grep -q instanceDir; do + echo "Could not find any cores, waiting..." + sleep 3 + done + + echo "Created core \"dovecot-fts\"" + + echo "Stopping local Solr" + su-exec solr stop-local-solr + + exit 0 +fi + +exec su-exec solr solr-foreground + diff --git a/data/Dockerfiles/solr/solr-config-7.7.0.xml b/data/Dockerfiles/solr/solr-config-7.7.0.xml new file mode 100644 index 00000000..3661874d --- /dev/null +++ b/data/Dockerfiles/solr/solr-config-7.7.0.xml @@ -0,0 +1,289 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<!-- This is the default config with stuff non-essential to Dovecot removed. --> + +<config> + <!-- Controls what version of Lucene various components of Solr + adhere to. Generally, you want to use the latest version to + get all bug fixes and improvements. It is highly recommended + that you fully re-index after changing this setting as it can + affect both how text is indexed and queried. + --> + <luceneMatchVersion>7.7.0</luceneMatchVersion> + + <!-- A 'dir' option by itself adds any files found in the directory + to the classpath, this is useful for including all jars in a + directory. + + When a 'regex' is specified in addition to a 'dir', only the + files in that directory which completely match the regex + (anchored on both ends) will be included. + + If a 'dir' option (with or without a regex) is used and nothing + is found that matches, a warning will be logged. + + The examples below can be used to load some solr-contribs along + with their external dependencies. + --> + <lib dir="${solr.install.dir:../../../..}/contrib/extraction/lib" regex=".*\.jar" /> + <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-cell-\d.*\.jar" /> + + <lib dir="${solr.install.dir:../../../..}/contrib/clustering/lib/" regex=".*\.jar" /> + <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-clustering-\d.*\.jar" /> + + <lib dir="${solr.install.dir:../../../..}/contrib/langid/lib/" regex=".*\.jar" /> + <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-langid-\d.*\.jar" /> + + <lib dir="${solr.install.dir:../../../..}/contrib/velocity/lib" regex=".*\.jar" /> + <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-velocity-\d.*\.jar" /> + + <!-- Data Directory + + Used to specify an alternate directory to hold all index data + other than the default ./data under the Solr home. If + replication is in use, this should match the replication + configuration. + --> + <dataDir>${solr.data.dir:}</dataDir> + + <!-- The default high-performance update handler --> + <updateHandler class="solr.DirectUpdateHandler2"> + + <!-- Enables a transaction log, used for real-time get, durability, and + and solr cloud replica recovery. The log can grow as big as + uncommitted changes to the index, so use of a hard autoCommit + is recommended (see below). + "dir" - the target directory for transaction logs, defaults to the + solr data directory. + "numVersionBuckets" - sets the number of buckets used to keep + track of max version values when checking for re-ordered + updates; increase this value to reduce the cost of + synchronizing access to version buckets during high-volume + indexing, this requires 8 bytes (long) * numVersionBuckets + of heap space per Solr core. + --> + <updateLog> + <str name="dir">${solr.ulog.dir:}</str> + <int name="numVersionBuckets">${solr.ulog.numVersionBuckets:65536}</int> + </updateLog> + + <!-- AutoCommit + + Perform a hard commit automatically under certain conditions. + Instead of enabling autoCommit, consider using "commitWithin" + when adding documents. + + http://wiki.apache.org/solr/UpdateXmlMessages + + maxDocs - Maximum number of documents to add since the last + commit before automatically triggering a new commit. + + maxTime - Maximum amount of time in ms that is allowed to pass + since a document was added before automatically + triggering a new commit. + openSearcher - if false, the commit causes recent index changes + to be flushed to stable storage, but does not cause a new + searcher to be opened to make those changes visible. + + If the updateLog is enabled, then it's highly recommended to + have some sort of hard autoCommit to limit the log size. + --> + <autoCommit> + <maxTime>${solr.autoCommit.maxTime:15000}</maxTime> + <openSearcher>false</openSearcher> + </autoCommit> + + <!-- softAutoCommit is like autoCommit except it causes a + 'soft' commit which only ensures that changes are visible + but does not ensure that data is synced to disk. This is + faster and more near-realtime friendly than a hard commit. + --> + <autoSoftCommit> + <maxTime>${solr.autoSoftCommit.maxTime:-1}</maxTime> + </autoSoftCommit> + + <!-- Update Related Event Listeners + + Various IndexWriter related events can trigger Listeners to + take actions. + + postCommit - fired after every commit or optimize command + postOptimize - fired after every optimize command + --> + + </updateHandler> + + <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Query section - these settings control query time things like caches + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> + <query> + <!-- Solr Internal Query Caches + + There are two implementations of cache available for Solr, + LRUCache, based on a synchronized LinkedHashMap, and + FastLRUCache, based on a ConcurrentHashMap. + + FastLRUCache has faster gets and slower puts in single + threaded operation and thus is generally faster than LRUCache + when the hit ratio of the cache is high (> 75%), and may be + faster under other scenarios on multi-cpu systems. + --> + + <!-- Filter Cache + + Cache used by SolrIndexSearcher for filters (DocSets), + unordered sets of *all* documents that match a query. When a + new searcher is opened, its caches may be prepopulated or + "autowarmed" using data from caches in the old searcher. + autowarmCount is the number of items to prepopulate. For + LRUCache, the autowarmed items will be the most recently + accessed items. + + Parameters: + class - the SolrCache implementation LRUCache or + (LRUCache or FastLRUCache) + size - the maximum number of entries in the cache + initialSize - the initial capacity (number of entries) of + the cache. (see java.util.HashMap) + autowarmCount - the number of entries to prepopulate from + and old cache. + maxRamMB - the maximum amount of RAM (in MB) that this cache is allowed + to occupy. Note that when this option is specified, the size + and initialSize parameters are ignored. + --> + <filterCache class="solr.FastLRUCache" + size="512" + initialSize="512" + autowarmCount="0"/> + + <!-- Query Result Cache + + Caches results of searches - ordered lists of document ids + (DocList) based on a query, a sort, and the range of documents requested. + Additional supported parameter by LRUCache: + maxRamMB - the maximum amount of RAM (in MB) that this cache is allowed + to occupy + --> + <queryResultCache class="solr.LRUCache" + size="512" + initialSize="512" + autowarmCount="0"/> + + <!-- Document Cache + + Caches Lucene Document objects (the stored fields for each + document). Since Lucene internal document ids are transient, + this cache will not be autowarmed. + --> + <documentCache class="solr.LRUCache" + size="512" + initialSize="512" + autowarmCount="0"/> + + <!-- custom cache currently used by block join --> + <cache name="perSegFilter" + class="solr.search.LRUCache" + size="10" + initialSize="0" + autowarmCount="10" + regenerator="solr.NoOpRegenerator" /> + + <!-- Lazy Field Loading + + If true, stored fields that are not requested will be loaded + lazily. This can result in a significant speed improvement + if the usual case is to not load all stored fields, + especially if the skipped fields are large compressed text + fields. + --> + <enableLazyFieldLoading>true</enableLazyFieldLoading> + + <!-- Result Window Size + + An optimization for use with the queryResultCache. When a search + is requested, a superset of the requested number of document ids + are collected. For example, if a search for a particular query + requests matching documents 10 through 19, and queryWindowSize is 50, + then documents 0 through 49 will be collected and cached. Any further + requests in that range can be satisfied via the cache. + --> + <queryResultWindowSize>20</queryResultWindowSize> + + <!-- Maximum number of documents to cache for any entry in the + queryResultCache. + --> + <queryResultMaxDocsCached>200</queryResultMaxDocsCached> + + <!-- Use Cold Searcher + + If a search request comes in and there is no current + registered searcher, then immediately register the still + warming searcher and use it. If "false" then all requests + will block until the first searcher is done warming. + --> + <useColdSearcher>false</useColdSearcher> + + </query> + + + <!-- Request Dispatcher + + This section contains instructions for how the SolrDispatchFilter + should behave when processing requests for this SolrCore. + + --> + <requestDispatcher> + <httpCaching never304="true" /> + </requestDispatcher> + + <!-- Request Handlers + + http://wiki.apache.org/solr/SolrRequestHandler + + Incoming queries will be dispatched to a specific handler by name + based on the path specified in the request. + + If a Request Handler is declared with startup="lazy", then it will + not be initialized until the first request that uses it. + + --> + <!-- SearchHandler + + http://wiki.apache.org/solr/SearchHandler + + For processing Search Queries, the primary Request Handler + provided with Solr is "SearchHandler" It delegates to a sequent + of SearchComponents (see below) and supports distributed + queries across multiple shards + --> + <requestHandler name="/select" class="solr.SearchHandler"> + <!-- default values for query parameters can be specified, these + will be overridden by parameters in the request + --> + <lst name="defaults"> + <str name="echoParams">explicit</str> + <int name="rows">10</int> + </lst> + </requestHandler> + + <initParams path="/update/**,/select"> + <lst name="defaults"> + <str name="df">_text_</str> + </lst> + </initParams> + + <!-- Response Writers + + http://wiki.apache.org/solr/QueryResponseWriter + + Request responses will be written using the writer specified by + the 'wt' request parameter matching the name of a registered + writer. + + The "default" writer is the default and will be used if 'wt' is + not specified in the request. + --> + <queryResponseWriter name="xml" + default="true" + class="solr.XMLResponseWriter" /> +</config> diff --git a/data/Dockerfiles/solr/solr-schema-7.7.0.xml b/data/Dockerfiles/solr/solr-schema-7.7.0.xml new file mode 100644 index 00000000..2c2e6343 --- /dev/null +++ b/data/Dockerfiles/solr/solr-schema-7.7.0.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<schema name="dovecot-fts" version="2.0"> + <fieldType name="string" class="solr.StrField" omitNorms="true" sortMissingLast="true"/> + <fieldType name="long" class="solr.LongPointField" positionIncrementGap="0"/> + <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/> + + <fieldType name="text" class="solr.TextField" autoGeneratePhraseQueries="true" positionIncrementGap="100"> + <analyzer type="index"> + <tokenizer class="solr.StandardTokenizerFactory"/> + <filter class="solr.EdgeNGramFilterFactory" minGramSize="3" maxGramSize="20"/> + <filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/> + <filter class="solr.WordDelimiterGraphFilterFactory" catenateNumbers="1" generateNumberParts="1" splitOnCaseChange="1" generateWordParts="1" splitOnNumerics="1" catenateAll="1" catenateWords="1"/> + <filter class="solr.FlattenGraphFilterFactory"/> + <filter class="solr.LowerCaseFilterFactory"/> + <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> + <filter class="solr.PorterStemFilterFactory"/> + </analyzer> + <analyzer type="query"> + <tokenizer class="solr.StandardTokenizerFactory"/> + <filter class="solr.SynonymGraphFilterFactory" expand="true" ignoreCase="true" synonyms="synonyms.txt"/> + <filter class="solr.FlattenGraphFilterFactory"/> + <filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/> + <filter class="solr.WordDelimiterGraphFilterFactory" catenateNumbers="1" generateNumberParts="1" splitOnCaseChange="1" generateWordParts="1" splitOnNumerics="1" catenateAll="1" catenateWords="1"/> + <filter class="solr.LowerCaseFilterFactory"/> + <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> + <filter class="solr.PorterStemFilterFactory"/> + </analyzer> + </fieldType> + + <field name="id" type="string" indexed="true" required="true" stored="true"/> + <field name="uid" type="long" indexed="true" required="true" stored="true"/> + <field name="box" type="string" indexed="true" required="true" stored="true"/> + <field name="user" type="string" indexed="true" required="true" stored="true"/> + + <field name="hdr" type="text" indexed="true" stored="false"/> + <field name="body" type="text" indexed="true" stored="false"/> + + <field name="from" type="text" indexed="true" stored="false"/> + <field name="to" type="text" indexed="true" stored="false"/> + <field name="cc" type="text" indexed="true" stored="false"/> + <field name="bcc" type="text" indexed="true" stored="false"/> + <field name="subject" type="text" indexed="true" stored="false"/> + + <!-- Used by Solr internally: --> + <field name="_version_" type="long" indexed="true" stored="true"/> + + <uniqueKey>id</uniqueKey> +</schema> diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index 72e86bc0..7658a8f8 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.6 +FROM alpine:3.9 LABEL maintainer "Andre Peters <andre.peters@servercow.de>" @@ -8,8 +8,10 @@ RUN apk add --update --no-cache \ bash \ openssl \ drill \ + tzdata \ && curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \ && chown root:unbound /etc/unbound \ + && adduser unbound tty \ && chmod 775 /etc/unbound EXPOSE 53/udp 53/tcp diff --git a/data/Dockerfiles/unbound/docker-entrypoint.sh b/data/Dockerfiles/unbound/docker-entrypoint.sh index b458cd8a..d179eaca 100755 --- a/data/Dockerfiles/unbound/docker-entrypoint.sh +++ b/data/Dockerfiles/unbound/docker-entrypoint.sh @@ -1,8 +1,11 @@ #!/bin/bash +echo "Setting console permissions..." +chown root:tty /dev/console +chmod g+rw /dev/console echo "Receiving anchor key..." /usr/sbin/unbound-anchor -a /etc/unbound/trusted-key.key echo "Receiving root hints..." curl -#o /etc/unbound/root.hints https://www.internic.net/domain/named.cache - +/usr/sbin/unbound-control-setup exec "$@" diff --git a/data/Dockerfiles/watchdog/Dockerfile b/data/Dockerfiles/watchdog/Dockerfile index 46130e5f..7ab29b68 100644 --- a/data/Dockerfiles/watchdog/Dockerfile +++ b/data/Dockerfiles/watchdog/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.6 +FROM alpine:3.9 LABEL maintainer "Andrรฉ Peters <andre.peters@servercow.de>" # Installation @@ -9,6 +9,7 @@ RUN apk add --update \ nagios-plugins-ping \ curl \ bash \ + coreutils \ jq \ fcgi \ nagios-plugins-mysql \ diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index c06abbc2..f56d3c4c 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -5,6 +5,8 @@ trap "kill 0" EXIT # Prepare BACKGROUND_TASKS=() +echo "Waiting for containers to settle..." +sleep 10 if [[ "${USE_WATCHDOG}" =~ ^([nN][oO]|[nN])+$ ]]; then echo -e "$(date) - USE_WATCHDOG=n, skipping watchdog..." @@ -30,65 +32,84 @@ progress() { PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} )) redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"service\":\"${SERVICE}\",\"lvl\":\"${PERCENT}\",\"hpnow\":\"${CURRENT}\",\"hptotal\":\"${TOTAL}\",\"hpdiff\":\"${DIFF}\"}" > /dev/null log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" no_redis + # Return 10 to indicate a dead service + [ ${CURRENT} -le 0 ] && return 10 } log_msg() { if [[ ${2} != "no_redis" ]]; then redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ - tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null + tr '\r\n%&;$"_[]{}-' ' ')\"}" > /dev/null fi echo $(date) $(printf '%s\n' "${1}") } function mail_error() { [[ -z ${1} ]] && return 1 - [[ -z ${2} ]] && return 2 - RCPT_DOMAIN=$(echo ${1} | awk -F @ {'print $NF'}) - RCPT_MX=$(dig +short ${RCPT_DOMAIN} mx | sort -n | awk '{print $2; exit}') - if [[ -z ${RCPT_MX} ]]; then - log_msg "Cannot determine MX for ${1}, skipping email notification..." - return 1 - fi - ./smtp-cli --missing-modules-ok \ - --subject="Watchdog: ${2} service hit the error rate limit" \ - --body-plain="Service was restarted, please check your mailcow installation." \ - --to=${1} \ - --from="watchdog@${MAILCOW_HOSTNAME}" \ - --server="${RCPT_MX}" \ - --hello-host=${MAILCOW_HOSTNAME} - log_msg "Sent notification email to ${1}" + [[ -z ${2} ]] && BODY="Service was restarted on $(date), please check your mailcow installation." || BODY="$(date) - ${2}" + WATCHDOG_NOTIFY_EMAIL=$(echo "${WATCHDOG_NOTIFY_EMAIL}" | sed 's/"//;s|"$||') + IFS=',' read -r -a MAIL_RCPTS <<< "${WATCHDOG_NOTIFY_EMAIL}" + for rcpt in "${MAIL_RCPTS[@]}"; do + RCPT_DOMAIN= + RCPT_MX= + RCPT_DOMAIN=$(echo ${rcpt} | awk -F @ {'print $NF'}) + RCPT_MX=$(dig +short ${RCPT_DOMAIN} mx | sort -n | awk '{print $2; exit}') + if [[ -z ${RCPT_MX} ]]; then + log_msg "Cannot determine MX for ${rcpt}, skipping email notification..." + return 1 + fi + [ -f "/tmp/${1}" ] && ATTACH="--attach /tmp/${1}@text/plain" || ATTACH= + ./smtp-cli --missing-modules-ok \ + --subject="Watchdog: ${1} hit the error rate limit" \ + --body-plain="${BODY}" \ + --to=${rcpt} \ + --from="watchdog@${MAILCOW_HOSTNAME}" \ + --server="${RCPT_MX}" \ + --hello-host=${MAILCOW_HOSTNAME} \ + ${ATTACH} + log_msg "Sent notification email to ${rcpt}" + done } - get_container_ip() { # ${1} is container CONTAINER_ID=() + CONTAINER_IPS=() CONTAINER_IP= LOOP_C=1 until [[ ${CONTAINER_IP} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || [[ ${LOOP_C} -gt 5 ]]; do - sleep 0.5 - # get long container id for exact match - CONTAINER_ID=($(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | .id")) - # returned id can have multiple elements (if scaled), shuffle for random test - CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf)) - if [[ ! -z ${CONTAINER_ID} ]]; then - for matched_container in "${CONTAINER_ID[@]}"; do - CONTAINER_IP=$(curl --silent http://dockerapi:8080/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress') - # grep will do nothing if one of these vars is empty - [[ -z ${CONTAINER_IP} ]] && continue - [[ -z ${IPV4_NETWORK} ]] && continue - # only return ips that are part of our network - if ! grep -q ${IPV4_NETWORK} <(echo ${CONTAINER_IP}); then - CONTAINER_IP= - fi - done + if [ ${IP_BY_DOCKER_API} -eq 0 ]; then + CONTAINER_IP=$(dig a "${1}" +short) + else + sleep 0.5 + # get long container id for exact match + CONTAINER_ID=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | .id")) + # returned id can have multiple elements (if scaled), shuffle for random test + CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf)) + if [[ ! -z ${CONTAINER_ID} ]]; then + for matched_container in "${CONTAINER_ID[@]}"; do + CONTAINER_IPS=($(curl --silent --insecure https://dockerapi/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress')) + for ip_match in "${CONTAINER_IPS[@]}"; do + # grep will do nothing if one of these vars is empty + [[ -z ${ip_match} ]] && continue + [[ -z ${IPV4_NETWORK} ]] && continue + # only return ips that are part of our network + if ! grep -q ${IPV4_NETWORK} <(echo ${ip_match}); then + continue + else + CONTAINER_IP=${ip_match} + break + fi + done + [[ ! -z ${CONTAINER_IP} ]] && break + done + fi fi LOOP_C=$((LOOP_C + 1)) done [[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP} } -# Check functions nginx_checks() { err_count=0 diff_c=0 @@ -96,15 +117,52 @@ nginx_checks() { # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/nginx-mailcow; echo "$(tail -50 /tmp/nginx-mailcow)" > /tmp/nginx-mailcow host_ip=$(get_container_ip nginx-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 2>> /tmp/nginx-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Nginx" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi + done + return 1 +} + +unbound_checks() { + err_count=0 + diff_c=0 + THRESHOLD=8 + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow + host_ip=$(get_container_ip unbound-mailcow) + err_c_cur=${err_count} + /usr/lib/nagios/plugins/check_dns -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + DNSSEC=$(dig com +dnssec | egrep 'flags:.+ad') + if [[ -z ${DNSSEC} ]]; then + echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2 + err_count=$(( ${err_count} + 1)) + else + echo "DNSSEC check succeeded" 2>> /tmp/unbound-mailcow 1>&2 + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Unbound" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi done return 1 } @@ -116,15 +174,21 @@ mysql_checks() { # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow host_ip=$(get_container_ip mysql-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_mysql -H ${host_ip} -P 3306 -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_mysql_query -H ${host_ip} -P 3306 -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_mysql -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_mysql_query -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi done return 1 } @@ -132,19 +196,24 @@ mysql_checks() { sogo_checks() { err_count=0 diff_c=0 - THRESHOLD=20 + THRESHOLD=10 # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/sogo-mailcow; echo "$(tail -50 /tmp/sogo-mailcow)" > /tmp/sogo-mailcow host_ip=$(get_container_ip sogo-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /WebServerResources/css/theme-default.css -p 9192 -R md-default-theme 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 -R "SOGo\.MainUI" 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 -R "SOGo\.MainUI" 2>> /tmp/sogo-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "SOGo" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi done return 1 } @@ -152,19 +221,50 @@ sogo_checks() { postfix_checks() { err_count=0 diff_c=0 - THRESHOLD=16 + THRESHOLD=8 # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do - host_ip=$(get_container_ip postfix-mailcow) + touch /tmp/postfix-mailcow; echo "$(tail -50 /tmp/postfix-mailcow)" > /tmp/postfix-mailcow + host_ip=$(get_container_ip postfix-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f "watchdog@invalid" -C "RCPT TO:null@localhost" -C DATA -C . -R 250 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -S 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f "watchdog@invalid" -C "RCPT TO:null@localhost" -C DATA -C . -R 250 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -S 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Postfix" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi + done + return 1 +} + +clamd_checks() { + err_count=0 + diff_c=0 + THRESHOLD=15 + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/clamd-mailcow; echo "$(tail -50 /tmp/clamd-mailcow)" > /tmp/clamd-mailcow + host_ip=$(get_container_ip clamd-mailcow) + err_c_cur=${err_count} + /usr/lib/nagios/plugins/check_clamd -4 -H ${host_ip} 2>> /tmp/clamd-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Clamd" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 30 )) + fi done return 1 } @@ -172,22 +272,28 @@ postfix_checks() { dovecot_checks() { err_count=0 diff_c=0 - THRESHOLD=24 + THRESHOLD=20 # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/dovecot-mailcow; echo "$(tail -50 /tmp/dovecot-mailcow)" > /tmp/dovecot-mailcow host_ip=$(get_container_ip dovecot-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog@invalid" -C "RCPT TO:<watchdog@invalid>" -L -R "User doesn't exist" 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 993 -S -e "OK " 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 143 -e "OK " 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 10001 -e "VERSION" 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 4190 -e "Dovecot ready" 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog@invalid" -C "RCPT TO:<watchdog@invalid>" -L -R "User doesn't exist" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 993 -S -e "OK " 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 143 -e "OK " 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 10001 -e "VERSION" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 4190 -e "Dovecot ready" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Dovecot" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi done return 1 } @@ -195,46 +301,143 @@ dovecot_checks() { phpfpm_checks() { err_count=0 diff_c=0 - THRESHOLD=10 + THRESHOLD=5 # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/php-fpm-mailcow; echo "$(tail -50 /tmp/php-fpm-mailcow)" > /tmp/php-fpm-mailcow host_ip=$(get_container_ip php-fpm-mailcow) err_c_cur=${err_count} - nc -z ${host_ip} 9001 ; err_count=$(( ${err_count} + ($? * 2))) - nc -z ${host_ip} 9002 ; err_count=$(( ${err_count} + ($? * 2))) - /usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9001 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9002 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "PHP-FPM" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi done return 1 } -rspamd_checks() { +ratelimit_checks() { err_count=0 diff_c=0 - THRESHOLD=10 + THRESHOLD=1 + RL_LOG_STATUS=$(redis-cli -h redis LRANGE RL_LOG 0 0 | jq .qid) # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + err_c_cur=${err_count} + RL_LOG_STATUS_PREV=${RL_LOG_STATUS} + RL_LOG_STATUS=$(redis-cli -h redis LRANGE RL_LOG 0 0 | jq .qid) + if [[ ${RL_LOG_STATUS_PREV} != ${RL_LOG_STATUS} ]]; then + err_count=$(( ${err_count} + 1 )) + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Ratelimit" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi + done + return 1 +} + +acme_checks() { + err_count=0 + diff_c=0 + THRESHOLD=1 + ACME_LOG_STATUS=$(redis-cli -h redis GET ACME_FAIL_TIME) + if [[ -z "${ACME_LOG_STATUS}" ]]; then + redis-cli -h redis SET ACME_FAIL_TIME 0 + ACME_LOG_STATUS=0 + fi + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + err_c_cur=${err_count} + ACME_LOG_STATUS_PREV=${ACME_LOG_STATUS} + ACME_LOG_STATUS=$(redis-cli -h redis GET ACME_FAIL_TIME) + if [[ ${ACME_LOG_STATUS_PREV} != ${ACME_LOG_STATUS} ]]; then + err_count=$(( ${err_count} + 1 )) + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "ACME" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi + done + return 1 +} + +ipv6nat_checks() { + err_count=0 + diff_c=0 + THRESHOLD=1 + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + err_c_cur=${err_count} + CONTAINERS=$(curl --silent --insecure https://dockerapi/containers/json) + IPV6NAT_CONTAINER_ID=$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\")) | .id") + if [[ ! -z ${IPV6NAT_CONTAINER_ID} ]]; then + LATEST_STARTED="$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\") | not)" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)" + LATEST_IPV6NAT="$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\"))" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)" + DIFFERENCE_START_TIME=$(expr ${LATEST_IPV6NAT} - ${LATEST_STARTED} 2>/dev/null) + if [[ "${DIFFERENCE_START_TIME}" -lt 30 ]]; then + err_count=$(( ${err_count} + 1 )) + fi + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "IPv6 NAT" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep 300 + fi + done + return 1 +} + + +rspamd_checks() { + err_count=0 + diff_c=0 + THRESHOLD=5 + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/rspamd-mailcow; echo "$(tail -50 /tmp/rspamd-mailcow)" > /tmp/rspamd-mailcow host_ip=$(get_container_ip rspamd-mailcow) err_c_cur=${err_count} - SCORE=$(/usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/scan -d ' -To: null@localhost + SCORE=$(echo 'To: null@localhost From: watchdog@localhost Empty -' | jq -rc .required_score) +' | usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .required_score) if [[ ${SCORE} != "9999" ]]; then - echo "Rspamd settings check failed" 1>&2 + echo "Rspamd settings check failed" 2>> /tmp/rspamd-mailcow 1>&2 err_count=$(( ${err_count} + 1)) else - echo "Rspamd settings check succeeded" 1>&2 + echo "Rspamd settings check succeeded" 2>> /tmp/rspamd-mailcow 1>&2 fi - /usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Rspamd" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} @@ -249,7 +452,6 @@ Empty while true; do if ! nginx_checks; then log_msg "Nginx hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "nginx-mailcow" echo nginx-mailcow > /tmp/com_pipe fi done @@ -260,7 +462,6 @@ BACKGROUND_TASKS+=($!) while true; do if ! mysql_checks; then log_msg "MySQL hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "mysql-mailcow" echo mysql-mailcow > /tmp/com_pipe fi done @@ -271,7 +472,6 @@ BACKGROUND_TASKS+=($!) while true; do if ! phpfpm_checks; then log_msg "PHP-FPM hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "php-fpm-mailcow" echo php-fpm-mailcow > /tmp/com_pipe fi done @@ -282,18 +482,40 @@ BACKGROUND_TASKS+=($!) while true; do if ! sogo_checks; then log_msg "SOGo hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "sogo-mailcow" echo sogo-mailcow > /tmp/com_pipe fi done ) & BACKGROUND_TASKS+=($!) +if [ ${CHECK_UNBOUND} -eq 1 ]; then +( +while true; do + if ! unbound_checks; then + log_msg "Unbound hit error limit" + echo unbound-mailcow > /tmp/com_pipe + fi +done +) & +BACKGROUND_TASKS+=($!) +fi + +if [[ "${SKIP_CLAMD}" =~ ^([nN][oO]|[nN])+$ ]]; then +( +while true; do + if ! clamd_checks; then + log_msg "Clamd hit error limit" + echo clamd-mailcow > /tmp/com_pipe + fi +done +) & +BACKGROUND_TASKS+=($!) +fi + ( while true; do if ! postfix_checks; then log_msg "Postfix hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "postfix-mailcow" echo postfix-mailcow > /tmp/com_pipe fi done @@ -304,7 +526,6 @@ BACKGROUND_TASKS+=($!) while true; do if ! dovecot_checks; then log_msg "Dovecot hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "dovecot-mailcow" echo dovecot-mailcow > /tmp/com_pipe fi done @@ -315,13 +536,42 @@ BACKGROUND_TASKS+=($!) while true; do if ! rspamd_checks; then log_msg "Rspamd hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "rspamd-mailcow" echo rspamd-mailcow > /tmp/com_pipe fi done ) & BACKGROUND_TASKS+=($!) +( +while true; do + if ! ratelimit_checks; then + log_msg "Ratelimit hit error limit" + echo ratelimit > /tmp/com_pipe + fi +done +) & +BACKGROUND_TASKS+=($!) + +( +while true; do + if ! acme_checks; then + log_msg "ACME client hit error limit" + echo acme-tiny > /tmp/com_pipe + fi +done +) & +BACKGROUND_TASKS+=($!) + +( +while true; do + if ! ipv6nat_checks; then + log_msg "IPv6 NAT warning: ipv6nat-mailcow container was not started at least 30s after siblings (not an error)" + echo ipv6nat-mailcow > /tmp/com_pipe + fi +done +) & +BACKGROUND_TASKS+=($!) + # Monitor watchdog agents, stop script when agents fails and wait for respawn by Docker (restart:always:n) ( while true; do @@ -338,12 +588,12 @@ done # Monitor dockerapi ( while true; do - while nc -z dockerapi 8080; do + while nc -z dockerapi 443; do sleep 3 done log_msg "Cannot find dockerapi-mailcow, waiting to recover..." kill -STOP ${BACKGROUND_TASKS[*]} - until nc -z dockerapi 8080; do + until nc -z dockerapi 443; do sleep 3 done kill -CONT ${BACKGROUND_TASKS[*]} @@ -354,17 +604,41 @@ done # Restart container when threshold limit reached while true; do CONTAINER_ID= + HAS_INITDB= read com_pipe_answer </tmp/com_pipe - if [[ ${com_pipe_answer} =~ .+-mailcow ]]; then + if [ -s "/tmp/${com_pipe_answer}" ]; then + cat "/tmp/${com_pipe_answer}" + fi + if [[ ${com_pipe_answer} == "ratelimit" ]]; then + log_msg "At least one ratelimit was applied" + [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "Please see mailcow UI logs for further information." + elif [[ ${com_pipe_answer} == "acme-tiny" ]]; then + log_msg "acme-tiny client returned non-zero exit code" + [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "Please check acme-mailcow for ruther information." + elif [[ ${com_pipe_answer} =~ .+-mailcow ]] || [[ ${com_pipe_answer} == "ipv6nat-mailcow" ]]; then kill -STOP ${BACKGROUND_TASKS[*]} sleep 3 - CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id") + CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id") if [[ ! -z ${CONTAINER_ID} ]]; then - log_msg "Sending restart command to ${CONTAINER_ID}..." - curl --silent -XPOST http://dockerapi:8080/containers/${CONTAINER_ID}/restart + if [[ "${com_pipe_answer}" == "php-fpm-mailcow" ]]; then + HAS_INITDB=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/top | jq '.msg.Processes[] | contains(["php -c /usr/local/etc/php -f /web/inc/init_db.inc.php"])' | grep true) + fi + S_RUNNING=$(($(date +%s) - $(curl --silent --insecure https://dockerapi/containers/${CONTAINER_ID}/json | jq .State.StartedAt | xargs -n1 date +%s -d))) + if [ ${S_RUNNING} -lt 120 ]; then + log_msg "Container is running for less than 120 seconds, skipping action..." + elif [[ ! -z ${HAS_INITDB} ]]; then + log_msg "Database is being initialized by php-fpm-mailcow, not restarting but delaying checks for a minute..." + sleep 60 + else + log_msg "Sending restart command to ${CONTAINER_ID}..." + curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/restart + if [[ ${com_pipe_answer} != "ipv6nat-mailcow" ]]; then + [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" + fi + log_msg "Wait for restarted container to settle and continue watching..." + sleep 35 + fi fi - log_msg "Wait for restarted container to settle and continue watching..." - sleep 30s kill -CONT ${BACKGROUND_TASKS[*]} kill -USR1 ${BACKGROUND_TASKS[*]} fi diff --git a/data/assets/mysql/docker-entrypoint.sh b/data/assets/mysql/docker-entrypoint.sh new file mode 100755 index 00000000..94e394ac --- /dev/null +++ b/data/assets/mysql/docker-entrypoint.sh @@ -0,0 +1,192 @@ +#!/bin/bash +set -eo pipefail +shopt -s nullglob + +openssl req -x509 -sha256 -newkey rsa:2048 -keyout /var/lib/mysql/sql.key -out /var/lib/mysql/sql.crt -days 3650 -nodes -subj '/CN=mysql' + +# if command starts with an option, prepend mysqld +if [ "${1:0:1}" = '-' ]; then + set -- mysqld "$@" +fi + +# skip setup if they want an option that stops mysqld +wantHelp= +for arg; do + case "$arg" in + -'?'|--help|--print-defaults|-V|--version) + wantHelp=1 + break + ;; + esac +done + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +_check_config() { + toRun=( "$@" --verbose --help --log-bin-index="$(mktemp -u)" ) + if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then + cat >&2 <<-EOM + + ERROR: mysqld failed while attempting to check config + command was: "${toRun[*]}" + + $errors + EOM + exit 1 + fi +} + +# Fetch value from server config +# We use mysqld --verbose --help instead of my_print_defaults because the +# latter only show values present in config files, and not server defaults +_get_config() { + local conf="$1"; shift + "$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null | awk '$1 == "'"$conf"'" { print $2; exit }' +} + +# allow the container to be started with `--user` +if [ "$1" = 'mysqld' -a -z "$wantHelp" -a "$(id -u)" = '0' ]; then + _check_config "$@" + DATADIR="$(_get_config 'datadir' "$@")" + mkdir -p "$DATADIR" + chown -R mysql:mysql "$DATADIR" + exec gosu mysql "$BASH_SOURCE" "$@" +fi + +if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then + # still need to check config, container may have started with --user + _check_config "$@" + # Get config + DATADIR="$(_get_config 'datadir' "$@")" + + if [ ! -d "$DATADIR/mysql" ]; then + file_env 'MYSQL_ROOT_PASSWORD' + if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then + echo >&2 'error: database is uninitialized and password option is not specified ' + echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD' + exit 1 + fi + + mkdir -p "$DATADIR" + + echo 'Initializing database' + # "Other options are passed to mysqld." (so we pass all "mysqld" arguments directly here) + mysql_install_db --datadir="$DATADIR" --rpm "${@:2}" + echo 'Database initialized' + + SOCKET="$(_get_config 'socket' "$@")" + "$@" --skip-networking --socket="${SOCKET}" & + pid="$!" + + mysql=( mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" ) + + for i in {30..0}; do + if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then + break + fi + echo 'MySQL init process in progress...' + sleep 1 + done + if [ "$i" = 0 ]; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then + # sed is for https://bugs.mysql.com/bug.php?id=20545 + mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql + fi + + if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then + export MYSQL_ROOT_PASSWORD="$(pwgen -1 32)" + echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD" + fi + + rootCreate= + # default root to listen for connections from anywhere + file_env 'MYSQL_ROOT_HOST' '%' + if [ ! -z "$MYSQL_ROOT_HOST" -a "$MYSQL_ROOT_HOST" != 'localhost' ]; then + # no, we don't care if read finds a terminating character in this heredoc + # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 + read -r -d '' rootCreate <<-EOSQL || true + CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; + GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ; + EOSQL + fi + + "${mysql[@]}" <<-EOSQL + -- What's done in this file shouldn't be replicated + -- or products like mysql-fabric won't work + SET @@SESSION.SQL_LOG_BIN=0; + + DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; + SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ; + GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ; + ${rootCreate} + DROP DATABASE IF EXISTS test ; + FLUSH PRIVILEGES ; + EOSQL + + if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then + mysql+=( -p"${MYSQL_ROOT_PASSWORD}" ) + fi + + file_env 'MYSQL_DATABASE' + if [ "$MYSQL_DATABASE" ]; then + echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}" + mysql+=( "$MYSQL_DATABASE" ) + fi + + file_env 'MYSQL_USER' + file_env 'MYSQL_PASSWORD' + if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then + echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" | "${mysql[@]}" + + if [ "$MYSQL_DATABASE" ]; then + echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}" + fi + fi + + echo + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sh) echo "$0: running $f"; . "$f" ;; + *.sql) echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;; + *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;; + *) echo "$0: ignoring $f" ;; + esac + echo + done + + if ! kill -s TERM "$pid" || ! wait "$pid"; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + echo + echo 'MySQL init process done. Ready for start up.' + echo + fi +fi + +exec "$@" diff --git a/data/assets/nextcloud/nextcloud.conf b/data/assets/nextcloud/nextcloud.conf index 72f30240..cf90a32b 100644 --- a/data/assets/nextcloud/nextcloud.conf +++ b/data/assets/nextcloud/nextcloud.conf @@ -5,11 +5,11 @@ map $http_x_forwarded_proto $client_req_scheme_nc { server { include /etc/nginx/conf.d/listen_ssl.active; + include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/mime.types; charset utf-8; override_charset on; - ssl on; ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; ssl_protocols TLSv1.2; @@ -24,7 +24,8 @@ server { add_header X-Robots-Tag none; add_header X-Download-Options noopen; add_header X-Permitted-Cross-Domain-Policies none; - add_header X-Frame-Options "SAMEORIGIN"; + #add_header X-Frame-Options "SAMEORIGIN"; + add_header Referrer-Policy "no-referrer"; server_name NC_SUBD; diff --git a/data/assets/templates/quarantine.tpl b/data/assets/templates/quarantine.tpl new file mode 100644 index 00000000..3af0e121 --- /dev/null +++ b/data/assets/templates/quarantine.tpl @@ -0,0 +1,49 @@ +<html> + <head> + <style> + body { + font-family: Helvetica, Arial, Sans-Serif; + } + table { + border-collapse: collapse; + width: 100%; + margin-bottom: 20px; + } + th, td { + padding: 8px; + text-align: left; + border-bottom: 1px solid #ddd; + vertical-align: top; + } + th { + background-color: #56B04C; + color: white; + } + tr:nth-child(even){background-color: #f2f2f2} + + </style> + </head> + <body> + <p>Hi!<br> + {% if counter == 1 %} + There is 1 new message waiting in quarantine:<br> + {% else %} + There are {{counter}} new messages waiting in quarantine:<br> + {% endif %} + <table> + <tr><th>Subject</th><th>Sender</th><th>Score</th><th>Arrived on</th>{% if quarantine_acl == 1 %}<th>Actions</th>{% endif %}</tr> + {% for line in meta %} + <tr> + <td>{{ line.subject|e }}</td> + <td>{{ line.sender|e }}</td> + <td>{{ line.score }}</td> + <td>{{ line.created }}</td> + {% if quarantine_acl == 1 %} + <td><a href="https://{{ hostname }}/qhandler/release/{{ line.qhash }}">release</a> | <a href="https://{{ hostname }}/qhandler/delete/{{ line.qhash }}">delete</a></td> + {% endif %} + </tr> + {% endfor %} + </table> + </p> + </body> +</html> diff --git a/data/assets/templates/quota.tpl b/data/assets/templates/quota.tpl new file mode 100644 index 00000000..eef5c92a --- /dev/null +++ b/data/assets/templates/quota.tpl @@ -0,0 +1,29 @@ +<html> + <head> + <style> + body { + font-family: sans-serif; + } + #progressbar { + background-color: #f0f0f0; + border-radius: 0px; + padding: 0px; + width:50%; + } + #progressbar > div { + background-color: #ff9c9c; + width: {{percent}}%; + height: 20px; + border-radius: 0px; + } + </style> + </head> + <body> + <p>Hi {{username}}!<br><br> + Your mailbox is now {{percent}}% full, please consider deleting old messages to still be able to receive new mails in the future.<br> + <div id="progressbar"> + <div></div> + </div> + </p> + </body> +</html> diff --git a/data/conf/clamav/clamd.conf b/data/conf/clamav/clamd.conf index f09510bd..0fe92e72 100644 --- a/data/conf/clamav/clamd.conf +++ b/data/conf/clamav/clamd.conf @@ -1,4 +1,5 @@ -LogFile /dev/console +#Debug true +#LogFile /dev/null LogTime yes LogClean yes ExtendedDetectionInfo yes @@ -23,9 +24,9 @@ DetectPUA yes #IncludePUA Spy #IncludePUA Scanner #IncludePUA RAT -AlgorithmicDetection yes +HeuristicAlerts yes ScanOLE2 yes -OLE2BlockMacros yes +AlertOLE2Macros no ScanPDF yes ScanSWF yes ScanXMLDOCS yes diff --git a/data/conf/clamav/freshclam.conf b/data/conf/clamav/freshclam.conf index f4fff582..50eae031 100644 --- a/data/conf/clamav/freshclam.conf +++ b/data/conf/clamav/freshclam.conf @@ -1,8 +1,7 @@ -UpdateLogFile /var/log/clamav/freshclam.log +#UpdateLogFile /dev/console LogTime yes PidFile /run/clamav/freshclam.pid DatabaseOwner clamav -AllowSupplementaryGroups yes DNSDatabaseInfo current.cvd.clamav.net DatabaseMirror database.clamav.net MaxAttempts 4 diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index dd8db8a3..cef0a19a 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -1,6 +1,12 @@ # -------------------------------------------------------------------------- # Please create a file "extra.conf" for persistent overrides to dovecot.conf # -------------------------------------------------------------------------- +# LDAP example: +#passdb { +# args = /usr/local/etc/dovecot/ldap/passdb.conf +# driver = ldap +#} + auth_mechanisms = plain login #mail_debug = yes #auth_debug = yes @@ -14,7 +20,10 @@ disable_plaintext_auth = yes login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k" mail_home = /var/vmail/%d/%n mail_location = maildir:~/ -mail_plugins = quota acl zlib listescape #mail_crypt +mail_plugins = </usr/local/etc/dovecot/mail_plugins +mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix: +mail_attachment_dir = /var/attachments +mail_attachment_min_size = 128k # Dovecot 2.2 #ssl_protocols = !SSLv3 @@ -45,6 +54,14 @@ passdb { passdb { args = /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf driver = sql + result_success = return-ok + result_failure = continue + result_internalfail = continue +} +passdb { + driver = passwd-file + args = /usr/local/etc/dovecot/dovecot-master.passwd + skip = authenticated } # Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing) service doveadm { @@ -72,6 +89,9 @@ namespace inbox { mailbox "Gelรถschte Objekte" { special_use = \Trash } + mailbox "Gelรถschte Elemente" { + special_use = \Trash + } mailbox "Papierkorb" { special_use = \Trash } @@ -125,6 +145,9 @@ namespace inbox { mailbox "Gesendete Objekte" { special_use = \Sent } + mailbox "Gesendete Elemente" { + special_use = \Sent + } mailbox "Itens Enviados" { special_use = \Sent } @@ -169,13 +192,25 @@ namespace inbox { mailbox "Ongewenste e-mail" { special_use = \Junk } + mailbox "Koncepty" { + special_use = \Drafts + } + mailbox "Nevyลพรกdanรก poลกta" { + special_use = \Junk + } + mailbox "Odstranฤnรก poลกta" { + special_use = \Trash + } + mailbox "Odeslanรก poลกta" { + special_use = \Sent + } prefix = } namespace { type = shared separator = / prefix = Shared/%%u/ - location = maildir:%%h/:CONTROL=~/Shared/%%u:INDEXPVT=~/Shared/%%u + location = maildir:%%h/:INDEX=~/Shared/%%u;CONTROL=~/Shared/%%u subscriptions = no list = children } @@ -190,6 +225,13 @@ service dict { service log { user = dovenull } +service config { + unix_listener config { + user = root + group = vmail + mode = 0660 + } +} service auth { inet_listener auth-inet { port = 10001 @@ -209,22 +251,22 @@ service managesieve-login { } service_count = 1 process_min_avail = 2 - vsz_limit = 256 M + vsz_limit = 1G } service imap-login { service_count = 1 - process_limit = 500 - vsz_limit = 256 M + process_limit = 10000 + vsz_limit = 1G user = dovenull } service pop3-login { service_count = 1 - vsz_limit = 256 M + vsz_limit = 1G } service imap { executable = imap imap-postlogin - user = dovenull - vsz_limit = 256 M + user = vmail + vsz_limit = 1G } service managesieve { process_limit = 256 @@ -238,17 +280,22 @@ service lmtp { listen = *,[::] ssl_cert = </etc/ssl/mail/cert.pem ssl_key = </etc/ssl/mail/key.pem +userdb { + driver = passwd-file + args = /usr/local/etc/dovecot/dovecot-master.userdb +} userdb { args = /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf driver = sql + skip = found } protocol imap { + mail_plugins = </usr/local/etc/dovecot/mail_plugins_imap imap_metadata = yes - mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape #mail_crypt } mail_attribute_dict = file:%h/dovecot-attributes protocol lmtp { - mail_plugins = quota sieve acl zlib listescape #mail_crypt + mail_plugins = </usr/local/etc/dovecot/mail_plugins_lmtp auth_socket_path = /usr/local/var/run/dovecot/auth-master } protocol sieve { @@ -256,9 +303,12 @@ protocol sieve { } plugin { # Allow "any" or "authenticated" to be used in ACLs - #acl_anyone = allow + acl_anyone = </usr/local/etc/dovecot/acl_anyone acl_shared_dict = file:/var/vmail/shared-mailboxes.db acl = vfile + fts = solr + fts_autoindex = yes + fts_solr = url=http://solr:8983/solr/dovecot-fts/ quota = dict:Userquota::proxy::sqlquota quota_rule2 = Trash:storage=+100%% sieve = /var/vmail/sieve/%u.sieve @@ -275,8 +325,11 @@ plugin { imapsieve_mailbox2_causes = COPY imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve # END + quota_warning = storage=95%% quota-warning 95 %u + quota_warning2 = storage=80%% quota-warning 80 %u sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve - sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute +vacation-seconds + sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute + sieve_extensions = +notify +imapflags +vacation-seconds sieve_max_script_size = 1M sieve_max_redirects = 30 sieve_quota_max_scripts = 0 @@ -288,11 +341,26 @@ plugin { sieve_before = dict:proxy::sieve_before;name=active;bindir=/var/vmail/sieve_before_bindir sieve_after = dict:proxy::sieve_after;name=active;bindir=/var/vmail/sieve_after_bindir sieve_after2 = /var/vmail/sieve/global.sieve - #mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem - #mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem - #mail_crypt_save_version = 2 + + # -- Global keys + mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem + mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem + mail_crypt_save_version = 2 + # Enable compression while saving, lz4 Dovecot v2.2.11+ zlib_save = lz4 + + mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename + mail_log_fields = uid box msgid size + mail_log_cached_only = yes +} +service quota-warning { + executable = script /usr/local/bin/quota_notify.py + # use some unprivileged user for executing the quota warnings + user = vmail + unix_listener quota-warning { + user = vmail + } } dict { sqlquota = mysql:/usr/local/etc/dovecot/sql/dovecot-dict-sql-quota.conf @@ -315,4 +383,11 @@ service stats { user = vmail } } +imap_max_line_length = 2 M +#auth_cache_verify_password_with_worker = yes +#auth_cache_negative_ttl = 0 +#auth_cache_ttl = 30 s +#auth_cache_size = 2 M !include_try /usr/local/etc/dovecot/extra.conf +!include_try /usr/local/etc/dovecot/sogo-sso.conf +default_client_limit = 10400 diff --git a/data/conf/dovecot/ldap/passdb.conf b/data/conf/dovecot/ldap/passdb.conf new file mode 100644 index 00000000..12fc3c05 --- /dev/null +++ b/data/conf/dovecot/ldap/passdb.conf @@ -0,0 +1,9 @@ +#hosts = 1.2.3.4 +#dn = cn=admin,dc=example,dc=local +#dnpass = password +#ldap_version = 3 +#base = ou=People,dc=example,dc=local +#auth_bind = no +#pass_filter = (&(objectClass=posixAccount)(mail=%u)) +#pass_attrs = mail=user,userPassword=password +#default_pass_scheme = SSHA diff --git a/data/conf/mysql/my.cnf b/data/conf/mysql/my.cnf index 772c8a62..a1c395a8 100644 --- a/data/conf/mysql/my.cnf +++ b/data/conf/mysql/my.cnf @@ -2,9 +2,9 @@ character-set-client-handshake = FALSE character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci -innodb_file_per_table = TRUE -innodb_file_format = barracuda -innodb_large_prefix = TRUE +#innodb_file_per_table = TRUE +#innodb_file_format = barracuda +#innodb_large_prefix = TRUE #sql_mode=IGNORE_SPACE,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION max_allowed_packet=192M max-connections=1500 diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index d25d3b61..ccb94fa0 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -7,15 +7,6 @@ map $http_x_forwarded_proto $client_req_scheme { https https; } -map $sent_http_content_type $expires { - default off; - text/html off; - text/css 1d; - application/javascript 1d; - application/json off; - image/png 1d; -} - server { include /etc/nginx/mime.types; charset utf-8; @@ -23,33 +14,68 @@ server { ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; - ssl_protocols TLSv1.2; + ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:50m; ssl_session_timeout 1d; ssl_session_tickets off; - add_header Strict-Transport-Security "max-age=15768000; includeSubDomains"; + add_header Strict-Transport-Security "max-age=15768000;"; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header X-Robots-Tag none; add_header X-Download-Options noopen; + add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies none; + add_header Referrer-Policy strict-origin; index index.php index.html; client_max_body_size 0; + listen 127.0.0.1:65510; include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_ssl.active; include /etc/nginx/conf.d/server_name.active; + gzip on; + gzip_disable "msie6"; + + gzip_vary on; + gzip_proxied off; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_min_length 256; + gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; + + location ~ ^/(fonts|js|css|img)/ { + expires max; + add_header Cache-Control public; + } + error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; absolute_redirect off; root /web; + location / { + try_files $uri $uri/ @strip-ext; + } + + location /qhandler { + rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2; + } + + location /edit { + rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2; + } + + location @strip-ext { + rewrite ^(.*)$ $1.php last; + } + location ~ ^/api/v1/(.*)$ { try_files $uri $uri/ /json_api.php?query=$1; } @@ -91,7 +117,6 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_redirect off; - expires $expires; } location ~* ^/Autodiscover/Autodiscover.xml { @@ -118,14 +143,26 @@ server { try_files /autoconfig.php =404; } + # auth_request endpoint if ALLOW_ADMIN_EMAIL_LOGIN is set + location /sogo-auth-verify { + internal; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $http_host; + proxy_set_header Content-Length ""; + proxy_pass http://127.0.0.1:65510/sogo-auth; + proxy_pass_request_body off; + } + location ^~ /Microsoft-Server-ActiveSync { + include /etc/nginx/conf.d/sogo_proxy_auth.active; include /etc/nginx/conf.d/sogo_eas.active; - proxy_connect_timeout 1000; + proxy_connect_timeout 4000; proxy_next_upstream timeout error; - proxy_send_timeout 1000; - proxy_read_timeout 1000; + proxy_send_timeout 4000; + proxy_read_timeout 4000; proxy_buffer_size 8k; - proxy_buffers 4 32k; + proxy_buffers 16 64k; proxy_temp_file_write_size 64k; proxy_busy_buffers_size 64k; proxy_set_header X-Real-IP $remote_addr; @@ -141,6 +178,7 @@ server { } location ^~ /SOGo { + include /etc/nginx/conf.d/sogo_proxy_auth.active; include /etc/nginx/conf.d/sogo.active; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -156,44 +194,19 @@ server { } location /SOGo.woa/WebServerResources/ { - proxy_pass http://sogo:9192/WebServerResources/; - proxy_set_header Host $http_host; - proxy_cache sogo; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; - #alias /usr/lib/GNUstep/SOGo/WebServerResources/; - expires $expires; - allow all; + alias /usr/lib/GNUstep/SOGo/WebServerResources/; } location /.woa/WebServerResources/ { - proxy_pass http://sogo:9192/WebServerResources/; - proxy_set_header Host $http_host; - proxy_cache sogo; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; - #alias /usr/lib/GNUstep/SOGo/WebServerResources/; - expires $expires; - allow all; + alias /usr/lib/GNUstep/SOGo/WebServerResources/; } location /SOGo/WebServerResources/ { - proxy_pass http://sogo:9192/WebServerResources/; - proxy_set_header Host $http_host; - proxy_cache sogo; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; - #alias /usr/lib/GNUstep/SOGo/WebServerResources/; - allow all; + alias /usr/lib/GNUstep/SOGo/WebServerResources/; } location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { - proxy_pass http://sogo:9192/$1.SOGo/Resources/$2; - proxy_set_header Host $http_host; - proxy_cache sogo; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; - #alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; + alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; } include /etc/nginx/conf.d/site.*.custom; diff --git a/data/conf/nginx/templates/sogo.auth_request.template.sh b/data/conf/nginx/templates/sogo.auth_request.template.sh new file mode 100644 index 00000000..f6d2d98e --- /dev/null +++ b/data/conf/nginx/templates/sogo.auth_request.template.sh @@ -0,0 +1,10 @@ +if printf "%s\n" "${ALLOW_ADMIN_EMAIL_LOGIN}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then + echo 'auth_request /sogo-auth-verify; +auth_request_set $user $upstream_http_x_user; +auth_request_set $auth $upstream_http_x_auth; +auth_request_set $auth_type $upstream_http_x_auth_type; +proxy_set_header x-webobjects-remote-user "$user"; +proxy_set_header Authorization "$auth"; +proxy_set_header x-webobjects-auth-type "$auth_type"; +' +fi diff --git a/data/conf/phpfpm/php-conf.d/other.ini b/data/conf/phpfpm/php-conf.d/other.ini index 2d05a5b7..4a90901d 100644 --- a/data/conf/phpfpm/php-conf.d/other.ini +++ b/data/conf/phpfpm/php-conf.d/other.ini @@ -1,2 +1,4 @@ session.save_handler = redis session.save_path = "tcp://redis:6379" +max_execution_time = 1200 +max_input_time = 1200 diff --git a/data/conf/phpfpm/php-fpm.d/pools.conf b/data/conf/phpfpm/php-fpm.d/pools.conf index 0667c2bc..77145a74 100644 --- a/data/conf/phpfpm/php-fpm.d/pools.conf +++ b/data/conf/phpfpm/php-fpm.d/pools.conf @@ -11,8 +11,7 @@ access.log = /proc/self/fd/2 clear_env = no catch_workers_output = yes php_admin_value[memory_limit] = 256M -php_admin_value[max_execution_time] = 1200 -php_admin_value[max_input_time] = 1200 +php_admin_value[disable_functions] = show_source, highlight_file, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, virtual, dl, disk_total_space, posix_getpwnam, posix_getpwuid, posix_mkfifo, posix_mknod, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_nice, openlog, syslog, pfsockopen, system, shell_exec, passthru, popen, proc_open, exec, ini_alter, pcntl_exec, proc_close, proc_get_status, proc_terminate, symlink [web-worker] user = www-data @@ -26,7 +25,5 @@ listen = [::]:9002 access.log = /proc/self/fd/2 clear_env = no catch_workers_output = yes -php_admin_value[memory_limit] = 256M -php_admin_value[max_execution_time] = 1200 -php_admin_value[max_input_time] = 1200 - +php_admin_value[memory_limit] = 512M +php_admin_value[disable_functions] = show_source, highlight_file, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, virtual, dl, disk_total_space, posix_getpwnam, posix_getpwuid, posix_mkfifo, posix_mknod, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_nice, openlog, syslog, pfsockopen, system, shell_exec, passthru, popen, proc_open, exec, ini_alter, pcntl_exec, proc_close, proc_get_status, proc_terminate, symlink diff --git a/data/conf/phpfpm/sogo-sso/.gitkeep b/data/conf/phpfpm/sogo-sso/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/conf/postfix/allow_mailcow_local.regexp b/data/conf/postfix/allow_mailcow_local.regexp new file mode 100644 index 00000000..0da4593c --- /dev/null +++ b/data/conf/postfix/allow_mailcow_local.regexp @@ -0,0 +1 @@ +/^(.+)@mailcow.local/ OK diff --git a/data/conf/postfix/anonymize_headers.pcre b/data/conf/postfix/anonymize_headers.pcre new file mode 100644 index 00000000..099094d9 --- /dev/null +++ b/data/conf/postfix/anonymize_headers.pcre @@ -0,0 +1,8 @@ +if /^\s*Received:.*Authenticated sender.*\(Postcow\)/ +/^\s*Received:.*Authenticated sender:(.+)/ + REPLACE Received: from localhost (localhost [127.0.0.1]) (Authenticated sender:$1 +endif +/^\s*X-Enigmail/ IGNORE +/^\s*X-Mailer/ IGNORE +/^\s*X-Originating-IP/ IGNORE +/^\s*X-Forward/ IGNORE diff --git a/data/conf/postfix/local_transport b/data/conf/postfix/local_transport new file mode 100644 index 00000000..5d10028c --- /dev/null +++ b/data/conf/postfix/local_transport @@ -0,0 +1 @@ +/localhost$/ local: diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index dc49e75e..88d905e7 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -20,7 +20,7 @@ broken_sasl_auth_clients = yes disable_vrfy_command = yes maximal_backoff_time = 1800s maximal_queue_lifetime = 1d -message_size_limit = 26214400 +message_size_limit = 104857600 milter_default_action = accept milter_protocol = 6 minimal_backoff_time = 300s @@ -41,8 +41,11 @@ postscreen_greet_wait = 3s postscreen_non_smtp_command_enable = no postscreen_pipelining_enable = no proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf, @@ -91,13 +94,18 @@ smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem smtpd_tls_eecdh_grade = auto smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA smtpd_tls_loglevel = 1 -smtp_tls_mandatory_protocols = !SSLv2, !SSLv3 + +smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_protocols = !SSLv2, !SSLv3 -lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3 -lmtp_tls_protocols = !SSLv2, !SSLv2, !SSLv3 -smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 + +lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +lmtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 + +smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtpd_tls_protocols = !SSLv2, !SSLv3 + smtpd_tls_security_level = may +tls_preempt_cipherlist = yes tls_ssl_options = NO_COMPRESSION smtpd_tls_mandatory_ciphers = high virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf, @@ -124,6 +132,11 @@ mydestination = localhost.localdomain, localhost smtp_address_preference = ipv4 smtp_sender_dependent_authentication = yes smtp_sasl_auth_enable = yes -smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf +smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf smtp_sasl_security_options = smtp_sasl_mechanism_filter = plain, login +smtp_tls_policy_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf +smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre +mail_name = Postcow +transport_maps = pcre:/opt/postfix/conf/local_transport, proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf +smtp_sasl_auth_soft_bounce = no diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf index 4911271e..fcc99717 100644 --- a/data/conf/postfix/master.cf +++ b/data/conf/postfix/master.cf @@ -1,17 +1,23 @@ smtp inet n - n - 1 postscreen smtpd pass - - n - - smtpd -o smtpd_helo_restrictions=permit_mynetworks,reject_non_fqdn_helo_hostname + -o smtpd_sasl_auth_enable=no + -o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain smtps inet n - n - - smtpd -o smtpd_tls_wrappermode=yes -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3 + -o tls_preempt_cipherlist=yes submission inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_enforce_tls=yes -o smtpd_tls_security_level=encrypt + -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3 -o tls_preempt_cipherlist=yes 588 inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_tls_auth_only=no + -o smtpd_sender_restrictions=check_sasl_access,regexp:/opt/postfix/conf/allow_mailcow_local.regexp,reject_authenticated_sender_login_mismatch,permit_mynetworks,permit_sasl_authenticated,reject_unlisted_sender,reject_unknown_sender_domain 590 inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_tls_auth_only=no @@ -21,6 +27,8 @@ smtp_enforced_tls unix - - n - - smtp -o smtp_tls_security_level=encrypt -o syslog_name=enforced-tls-smtp -o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter +smtp_via_transport_maps unix - - n - - smtp + -o smtp_sasl_password_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf tlsproxy unix - - n - 0 tlsproxy dnsblog unix - - n - 0 dnsblog diff --git a/data/conf/rspamd/custom/bad_asn.map b/data/conf/rspamd/custom/bad_asn.map index 8b7a4407..ffd9af0f 100644 --- a/data/conf/rspamd/custom/bad_asn.map +++ b/data/conf/rspamd/custom/bad_asn.map @@ -1,11 +1,12 @@ # High spam networks, disabled by default +# ASN:SCORE DESC +# Remove comment to enable score #201942:5 #Soltia Consulting SL - ipinfo.io -#16276:5 #OVH -#12876:5 #ONLINE S.A.S -#31034:5 -#12874:5 -#30823:5 -#197071:5 +#16276:2 #OVH +#12876:2 #ONLINE S.A.S +#31034:5 #ARUBA-ASN, IT +#12874:5 #FASTWEB, IT +#30823:3 #PKV spam #42831:5 #UK Dedicated Servers Ltd #29119:5 #Aire Networks del Mediterraneo S.L.U. #13335:5 #Cloudflare @@ -17,7 +18,7 @@ #14061:4 #Digitalocean #55293:4 #A2 Hosting #63018:4 #US Dedicated -#197518:2 +#197518:2 #RACKMARKT #44493:2 #46606:2 #49505:2 @@ -25,3 +26,5 @@ #197695:2 #198068:2 #43146:2 +#49100:4 +#39364:4 diff --git a/data/conf/rspamd/custom/global_mime_from_blacklist.map b/data/conf/rspamd/custom/global_mime_from_blacklist.map new file mode 100644 index 00000000..3c872889 --- /dev/null +++ b/data/conf/rspamd/custom/global_mime_from_blacklist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_mime_from_whitelist.map b/data/conf/rspamd/custom/global_mime_from_whitelist.map new file mode 100644 index 00000000..3c872889 --- /dev/null +++ b/data/conf/rspamd/custom/global_mime_from_whitelist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_rcpt_blacklist.map b/data/conf/rspamd/custom/global_rcpt_blacklist.map new file mode 100644 index 00000000..3c872889 --- /dev/null +++ b/data/conf/rspamd/custom/global_rcpt_blacklist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_rcpt_whitelist.map b/data/conf/rspamd/custom/global_rcpt_whitelist.map new file mode 100644 index 00000000..3c872889 --- /dev/null +++ b/data/conf/rspamd/custom/global_rcpt_whitelist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_smtp_from_blacklist.map b/data/conf/rspamd/custom/global_smtp_from_blacklist.map new file mode 100644 index 00000000..3c872889 --- /dev/null +++ b/data/conf/rspamd/custom/global_smtp_from_blacklist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_smtp_from_whitelist.map b/data/conf/rspamd/custom/global_smtp_from_whitelist.map new file mode 100644 index 00000000..3c872889 --- /dev/null +++ b/data/conf/rspamd/custom/global_smtp_from_whitelist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php index 5f83da81..a2be8ceb 100644 --- a/data/conf/rspamd/dynmaps/settings.php +++ b/data/conf/rspamd/dynmaps/settings.php @@ -6,10 +6,13 @@ then any of these will trigger the rule. If a rule is triggered then no more rul */ header('Content-Type: text/plain'); require_once "vars.inc.php"; +// Getting headers sent by the client. +//$headers = apache_request_headers(); ini_set('error_reporting', 0); -$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name; +//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name; +$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, @@ -24,14 +27,50 @@ catch (PDOException $e) { exit; } +// Check if db changed and return header +/*$stmt = $pdo->prepare("SELECT UNIX_TIMESTAMP(UPDATE_TIME) AS `db_update_time` FROM information_schema.tables + WHERE `TABLE_NAME` = 'filterconf' + AND TABLE_SCHEMA = :dbname;"); +$stmt->execute(array( + ':dbname' => $database_name +)); +$db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time']; + +if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == $db_update_time)) { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304); + exit; +} else { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200); +} +*/ + function parse_email($email) { - if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a)); } +function wl_by_sogo() { + global $pdo; + $rcpt = array(); + $stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info` + INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id` + GROUP BY `c_path2`"); + $sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($sogo_contacts)) { + foreach (explode(',', $row['contacts']) as $contact) { + if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) { + continue; + } + $rcpt[$row['user']][] = '/^' . str_replace('/', '\/', $contact) . '$/i'; + } + } + return $rcpt; +} + function ucl_rcpts($object, $type) { global $pdo; + $rcpt = array(); if ($type == 'mailbox') { // Standard aliases $stmt = $pdo->prepare("SELECT `address` FROM `alias` @@ -81,17 +120,14 @@ function ucl_rcpts($object, $type) { $rcpt[] = '/.*@' . $row['alias_domain'] . '/i'; } } - if (!empty($rcpt)) { - return $rcpt; - } - return false; + return $rcpt; } ?> settings { watchdog { priority = 10; - rcpt = "/null@localhost/i"; - from = "/watchdog@localhost/i"; + rcpt_mime = "/null@localhost/i"; + from_mime = "/watchdog@localhost/i"; apply "default" { actions { reject = 9999.0; @@ -137,6 +173,40 @@ while ($row = array_shift($rows)) { <?php } +/* +// Start SOGo contacts whitelist +// Priority 4, lower than a domain whitelist (5) and lower than a mailbox whitelist (6) +*/ + +foreach (wl_by_sogo() as $user => $contacts) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user); +?> + whitelist_sogo_<?=$username_sane;?> { +<?php + foreach ($contacts as $contact) { +?> + from = <?=json_encode($contact, JSON_UNESCAPED_SLASHES);?>; +<?php + } +?> + priority = 4; +<?php + foreach (ucl_rcpts($user, 'mailbox') as $rcpt) { +?> + rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>; +<?php + } +?> + apply "default" { + SOGO_CONTACT = -99.0; + } + symbols [ + "SOGO_CONTACT" + ] + } +<?php +} + /* // Start whitelist */ @@ -148,15 +218,17 @@ while ($row = array_shift($rows)) { ?> whitelist_<?=$username_sane;?> { <?php - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('^', `value`, '$'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` + $list_items = array(); + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object`= :object AND `option` = 'whitelist_from'"); $stmt->execute(array(':object' => $row['object'])); - $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); - $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); + $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($list_items as $item) { ?> - from = "/(<?=$value_sane;?>)/i"; + from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i"; <?php + } if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) { ?> priority = 5; @@ -185,19 +257,13 @@ while ($row = array_shift($rows)) { "MAILCOW_WHITE" ] } - whitelist_header_<?=$username_sane;?> { + whitelist_mime_<?=$username_sane;?> { <?php - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('\<', `value`, '\>'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` - WHERE `object`= :object - AND `option` = 'whitelist_from'"); - $stmt->execute(array(':object' => $row['object'])); - $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); - $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); + foreach ($list_items as $item) { ?> - header = { - "From" = "/(<?=$value_sane;?>)/i"; - } + from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i"; <?php + } if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) { ?> priority = 5; @@ -240,15 +306,17 @@ while ($row = array_shift($rows)) { ?> blacklist_<?=$username_sane;?> { <?php - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('^', `value`, '$'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` + $list_items = array(); + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object`= :object AND `option` = 'blacklist_from'"); $stmt->execute(array(':object' => $row['object'])); - $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); - $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); + $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($list_items as $item) { ?> - from = "/(<?=$value_sane;?>)/i"; + from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i"; <?php + } if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) { ?> priority = 5; @@ -279,17 +347,11 @@ while ($row = array_shift($rows)) { } blacklist_header_<?=$username_sane;?> { <?php - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('\<', `value`, '\>'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` - WHERE `object`= :object - AND `option` = 'blacklist_from'"); - $stmt->execute(array(':object' => $row['object'])); - $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); - $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); + foreach ($list_items as $item) { ?> - header = { - "From" = "/(<?=$value_sane;?>)/i"; - } + from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i"; <?php + } if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) { ?> priority = 5; @@ -342,7 +404,6 @@ while ($row = array_shift($rows)) { priority = 9; want_spam = yes; } - <?php // Start additional content @@ -357,6 +418,9 @@ while ($row = array_shift($rows)) { foreach ($content as $line) { echo ' ' . $line . PHP_EOL; } +?> + } +<?php } ?> } diff --git a/data/conf/rspamd/local.d/antivirus.conf b/data/conf/rspamd/local.d/antivirus.conf index 157c3cb9..c8d31d1e 100644 --- a/data/conf/rspamd/local.d/antivirus.conf +++ b/data/conf/rspamd/local.d/antivirus.conf @@ -1,7 +1,11 @@ clamav { - attachments_only = true; + # Scan whole message + scan_mime_parts = false; + #scan_text_mime = true; + #scan_image_mime = true; symbol = "CLAM_VIRUS"; type = "clamav"; log_clean = true; servers = "clamd:3310"; + max_size = 20971520; } diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf index dd47be35..d775c4f6 100644 --- a/data/conf/rspamd/local.d/composites.conf +++ b/data/conf/rspamd/local.d/composites.conf @@ -1,5 +1,5 @@ MX_IMPLICIT { - expression = "MX_GOOD and MX_MISSING"; + expression = "MX_GOOD & MX_MISSING"; score = -0.01; } VIRUS_FOUND { @@ -10,3 +10,9 @@ SPF_FAIL_NO_DKIM { expression = "R_SPF_FAIL & R_DKIM_NA & !MAILCOW_WHITE"; score = 10; } +SOGO_CONTACT_EXCLUDE_FWD_HOST { + expression = "WHITELISTED_FWD_HOST & ~SOGO_CONTACT"; +} +SOGO_CONTACT_SPOOFED { + expression = "(R_SPF_PERMFAIL | R_SPF_SOFTFAIL | R_SPF_FAIL) & ~SOGO_CONTACT"; +} diff --git a/data/conf/rspamd/local.d/fuzzy_group.conf b/data/conf/rspamd/local.d/fuzzy_group.conf index 6b3390c6..f3effe4e 100644 --- a/data/conf/rspamd/local.d/fuzzy_group.conf +++ b/data/conf/rspamd/local.d/fuzzy_group.conf @@ -3,9 +3,9 @@ symbols = { weight = 2.0; } "LOCAL_FUZZY_DENIED" { - weight = 10.0; + weight = 15.0; } "LOCAL_FUZZY_WHITE" { - weight = -3.4; + weight = -10.0; } } diff --git a/data/conf/rspamd/local.d/history_redis.conf b/data/conf/rspamd/local.d/history_redis.conf new file mode 100644 index 00000000..68a59b0c --- /dev/null +++ b/data/conf/rspamd/local.d/history_redis.conf @@ -0,0 +1 @@ +nrows = 1000; diff --git a/data/conf/rspamd/local.d/metadata_exporter.conf b/data/conf/rspamd/local.d/metadata_exporter.conf index f1600708..afe5c7e1 100644 --- a/data/conf/rspamd/local.d/metadata_exporter.conf +++ b/data/conf/rspamd/local.d/metadata_exporter.conf @@ -1,10 +1,26 @@ rules { - QUARANTINE { - backend = "http"; - url = "http://nginx:9081/pipe.php"; - selector = "is_reject"; - formatter = "default"; - meta_headers = true; - } + QUARANTINE { + backend = "http"; + url = "http://nginx:9081/pipe.php"; + selector = "is_reject"; + formatter = "default"; + meta_headers = true; + } + RLINFO { + backend = "http"; + url = "http://nginx:9081/pipe_rl.php"; + selector = "ratelimited"; + formatter = "json"; + } +} +custom_select { + ratelimited = <<EOD +return function(task) + local ratelimited = task:get_symbol("RATELIMITED") + if ratelimited then + return true + end + return +end +EOD; } - diff --git a/data/conf/rspamd/local.d/metrics.conf b/data/conf/rspamd/local.d/metrics.conf index 1e5a0fb3..7ef179e4 100644 --- a/data/conf/rspamd/local.d/metrics.conf +++ b/data/conf/rspamd/local.d/metrics.conf @@ -1,7 +1,7 @@ actions { reject = 15; - add_header = 5; - greylist = 4; + add_header = 8; + greylist = 7; } symbol "MAILCOW_AUTH" { @@ -13,25 +13,19 @@ group "MX" { symbol "MX_INVALID" { score = 0.5; description = "No connectable MX"; - one_shot = "true"; + one_shot = true; } symbol "MX_MISSING" { score = 2.0; description = "No MX record"; - one_shot = "true"; + one_shot = true; } symbol "MX_GOOD" { score = -0.01; description = "MX was ok"; - one_shot = "true"; + one_shot = true; } } - -symbol "SPOOFED_SENDER" { - description = "Sender is not authenticated but part of mailcow managed domains"; - score = 1.0; -} - symbol "CTYPE_MIXED_BOGUS" { score = 0.0; } diff --git a/data/conf/rspamd/local.d/milter_headers.conf b/data/conf/rspamd/local.d/milter_headers.conf index 0ffd6b36..7f21b8e0 100644 --- a/data/conf/rspamd/local.d/milter_headers.conf +++ b/data/conf/rspamd/local.d/milter_headers.conf @@ -7,6 +7,9 @@ routines { value = "YES"; remove = 1; } + fuzzy-hashes { + header = "X-Rspamd-Fuzzy"; + } authentication-results { header = "Authentication-Results"; remove = 1; diff --git a/data/conf/rspamd/local.d/multimap.conf b/data/conf/rspamd/local.d/multimap.conf index da4afaf2..7752b813 100644 --- a/data/conf/rspamd/local.d/multimap.conf +++ b/data/conf/rspamd/local.d/multimap.conf @@ -25,13 +25,6 @@ WHITELISTED_FWD_HOST { symbols_set = ["WHITELISTED_FWD_HOST"]; } -KEEP_SPAM { - type = "ip"; - map = "redis://KEEP_SPAM"; - action = "accept"; - symbols_set = ["KEEP_SPAM"]; -} - LOCAL_BL_ASN { require_symbols = "!MAILCOW_WHITE"; type = "asn"; @@ -41,10 +34,52 @@ LOCAL_BL_ASN { symbols_set = ["LOCAL_BL_ASN"]; } -#SPOOFED_SENDER { -# type = "rcpt"; -# filter = "email:domain:tld"; -# map = "redis://DOMAIN_MAP"; -# require_symbols = "AUTH_NA | !RCVD_VIA_SMTP_AUTH"; -# symbols_set = ["SPOOFED_SENDER"]; -#} +GLOBAL_SMTP_FROM_WL { + type = "from"; + map = "$LOCAL_CONFDIR/custom/global_smtp_from_whitelist.map"; + regexp = true; + prefilter = true; + action = "accept"; +} + +GLOBAL_SMTP_FROM_BL { + type = "from"; + map = "$LOCAL_CONFDIR/custom/global_smtp_from_blacklist.map"; + regexp = true; + prefilter = true; + action = "reject"; +} + +GLOBAL_MIME_FROM_WL { + type = "header"; + header = "from"; + map = "$LOCAL_CONFDIR/custom/global_mime_from_whitelist.map"; + regexp = true; + prefilter = true; + action = "accept"; +} + +GLOBAL_MIME_FROM_BL { + type = "header"; + header = "from"; + map = "$LOCAL_CONFDIR/custom/global_mime_from_blacklist.map"; + regexp = true; + prefilter = true; + action = "reject"; +} + +GLOBAL_RCPT_WL { + type = "rcpt"; + map = "$LOCAL_CONFDIR/custom/global_rcpt_whitelist.map"; + regexp = true; + prefilter = true; + action = "accept"; +} + +GLOBAL_RCPT_BL { + type = "rcpt"; + map = "$LOCAL_CONFDIR/custom/global_rcpt_blacklist.map"; + regexp = true; + prefilter = true; + action = "reject"; +} diff --git a/data/conf/rspamd/local.d/options.inc b/data/conf/rspamd/local.d/options.inc index 32f8dd9c..4fbdfba7 100644 --- a/data/conf/rspamd/local.d/options.inc +++ b/data/conf/rspamd/local.d/options.inc @@ -3,7 +3,7 @@ dns { } map_watch_interval = 30s; dns { - timeout = 15s; - retransmits = 5; + timeout = 4s; + retransmits = 2; } disable_monitoring = true; diff --git a/data/conf/rspamd/local.d/policies_group.conf b/data/conf/rspamd/local.d/policies_group.conf index deaf3f0f..f1c2b9f1 100644 --- a/data/conf/rspamd/local.d/policies_group.conf +++ b/data/conf/rspamd/local.d/policies_group.conf @@ -3,6 +3,15 @@ symbols = { score = 0.0; } "R_SPF_FAIL" { - score = 4.0; + score = 10.0; + } + "R_SPF_PERMFAIL" { + score = 10.0; + } + "R_DKIM_REJECT" { + score = 10.0; + } + "R_DKIM_PERMFAIL" { + score = 10.0; } } diff --git a/data/conf/rspamd/local.d/rspamd.conf.local b/data/conf/rspamd/local.d/rspamd.conf.local deleted file mode 100644 index 0662c47d..00000000 --- a/data/conf/rspamd/local.d/rspamd.conf.local +++ /dev/null @@ -1,16 +0,0 @@ -# rspamd.conf.local - -worker "fuzzy" { - # Socket to listen on (UDP and TCP from rspamd 1.3) - bind_socket = "*:11445"; - allow_update = ["127.0.0.1", "::1"]; - # Number of processes to serve this storage (useful for read scaling) - count = 2; - # Backend ("sqlite" or "redis" - default "sqlite") - backend = "redis"; - # Hashes storage time (3 months) - expire = 90d; - # Synchronize updates to the storage each minute - sync = 1min; -} - diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua index 7c9efbb4..c6fc0b98 100644 --- a/data/conf/rspamd/lua/rspamd.local.lua +++ b/data/conf/rspamd/lua/rspamd.local.lua @@ -7,6 +7,68 @@ rspamd_config.MAILCOW_AUTH = { end } +rspamd_config:register_symbol({ + name = 'KEEP_SPAM', + type = 'prefilter', + callback = function(task) + local util = require("rspamd_util") + local rspamd_logger = require "rspamd_logger" + local rspamd_ip = require 'rspamd_ip' + local uname = task:get_user() + + if uname then + return false + end + + local redis_params = rspamd_parse_redis_server('keep_spam') + local ip = task:get_from_ip() + + if not ip:is_valid() then + return false + end + + local from_ip_string = tostring(ip) + ip_check_table = {from_ip_string} + + local maxbits = 128 + local minbits = 32 + if ip:get_version() == 4 then + maxbits = 32 + minbits = 8 + end + for i=maxbits,minbits,-1 do + local nip = ip:apply_mask(i):to_string() .. "/" .. i + table.insert(ip_check_table, nip) + end + local function keep_spam_cb(err, data) + if err then + rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err) + return false + else + for k,v in pairs(data) do + if (v and v ~= userdata and v == '1') then + rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result", v) + task:set_pre_result('accept', 'IP matched with forward hosts') + end + end + end + end + table.insert(ip_check_table, 1, 'KEEP_SPAM') + local redis_ret_user = rspamd_redis_make_request(task, + redis_params, -- connect params + 'KEEP_SPAM', -- hash key + false, -- is write + keep_spam_cb, --callback + 'HMGET', -- command + ip_check_table -- arguments + ) + if not redis_ret_user then + rspamd_logger.infox(rspamd_config, "cannot check keep_spam redis map") + end + end, + priority = 19 +}) + rspamd_config:register_symbol({ name = 'TAG_MOO', type = 'postfilter', diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php index 7064b7b8..692a0c2e 100644 --- a/data/conf/rspamd/meta_exporter/pipe.php +++ b/data/conf/rspamd/meta_exporter/pipe.php @@ -6,7 +6,8 @@ require_once "vars.inc.php"; // Do not show errors, we log to using error_log ini_set('error_reporting', 0); // Init database -$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name; +//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name; +$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, @@ -16,6 +17,7 @@ try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { + error_log("QUARANTINE: " . $e); http_response_code(501); exit; } @@ -49,6 +51,7 @@ $raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8"); $headers = getallheaders(); $qid = $headers['X-Rspamd-Qid']; +$subject = $headers['X-Rspamd-Subject']; $score = $headers['X-Rspamd-Score']; $rcpts = $headers['X-Rspamd-Rcpt']; $user = $headers['X-Rspamd-User']; @@ -60,12 +63,11 @@ $symbols = $headers['X-Rspamd-Symbols']; $raw_size = (int)$_SERVER['CONTENT_LENGTH']; try { - if ($max_size = $redis->Get('Q_MAX_SIZE')) { - if (!empty($max_size) && ($max_size * 1048576) < $raw_size) { - error_log(sprintf("Message too large: %d exceeds %d", $raw_size, ($max_size * 1048576))); - http_response_code(505); - exit; - } + $max_size = (int)$redis->Get('Q_MAX_SIZE'); + if (($max_size * 1048576) < $raw_size) { + error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576))); + http_response_code(505); + exit; } if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) { $exclude_domains = json_decode($exclude_domains, true); @@ -73,7 +75,7 @@ try { $retention_size = (int)$redis->Get('Q_RETENTION_SIZE'); } catch (RedisException $e) { - error_log($e); + error_log("QUARANTINE: " . $e); http_response_code(504); exit; } @@ -82,6 +84,9 @@ $rcpt_final_mailboxes = array(); // Loop through all rcpts foreach (json_decode($rcpts, true) as $rcpt) { + // Remove tag + $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); + // Break rcpt into local part and domain part $parsed_rcpt = parse_email($rcpt); @@ -92,14 +97,14 @@ foreach (json_decode($rcpts, true) as $rcpt) { } } catch (RedisException $e) { - error_log($e); + error_log("QUARANTINE: " . $e); http_response_code(504); exit; } // Skip if domain is excluded if (in_array($parsed_rcpt['domain'], $exclude_domains)) { - error_log(sprintf("Skipped domain %s", $parsed_rcpt['domain'])); + error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain'])); continue; } @@ -134,12 +139,12 @@ foreach (json_decode($rcpts, true) as $rcpt) { // Loop through all found gotos foreach ($gotos_array as $index => &$goto) { - error_log("quarantine pipe: query " . $goto . " as username from mailbox"); + error_log("QUARANTINE: quarantine pipe: query " . $goto . " as username from mailbox"); $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND `active`= '1';"); $stmt->execute(array(':goto' => $goto)); $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; if (!empty($username)) { - error_log("quarantine pipe: mailbox found: " . $username); + error_log("QUARANTINE: quarantine pipe: mailbox found: " . $username); // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate if (!in_array($username, $rcpt_final_mailboxes)) { $rcpt_final_mailboxes[] = $username; @@ -148,13 +153,13 @@ foreach (json_decode($rcpts, true) as $rcpt) { else { $parsed_goto = parse_email($goto); if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { - error_log($goto . " is not a mailcow handled mailbox or alias address"); + error_log("QUARANTINE:" . $goto . " is not a mailcow handled mailbox or alias address"); } else { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); $stmt->execute(array(':goto' => $goto)); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - error_log("quarantine pipe: goto address " . $goto . " is a alias branch for " . $goto_branch); + error_log("QUARANTINE: quarantine pipe: goto address " . $goto . " is a alias branch for " . $goto_branch); $goto_branch_array = explode(',', $goto_branch); } } @@ -174,23 +179,24 @@ foreach (json_decode($rcpts, true) as $rcpt) { // Force exit if loop cannot be solved // Postfix does not allow for alias loops, so this should never happen. $loop_c++; - error_log("quarantine pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array)); + error_log("QUARANTINE: quarantine pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array)); } } catch (PDOException $e) { - error_log($e->getMessage()); + error_log("QUARANTINE: " . $e->getMessage()); http_response_code(502); exit; } } foreach ($rcpt_final_mailboxes as $rcpt) { - error_log("quarantine pipe: processing quarantine message for rcpt " . $rcpt); + error_log("QUARANTINE: quarantine pipe: processing quarantine message for rcpt " . $rcpt); try { - $stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`) - VALUES (:qid, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action)"); + $stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `subject`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`) + VALUES (:qid, :subject, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action)"); $stmt->execute(array( ':qid' => $qid, + ':subject' => $subject, ':score' => $score, ':sender' => $sender, ':rcpt' => $rcpt, @@ -217,7 +223,7 @@ foreach ($rcpt_final_mailboxes as $rcpt) { )); } catch (PDOException $e) { - error_log($e->getMessage()); + error_log("QUARANTINE: " . $e->getMessage()); http_response_code(503); exit; } diff --git a/data/conf/rspamd/meta_exporter/pipe_rl.php b/data/conf/rspamd/meta_exporter/pipe_rl.php new file mode 100644 index 00000000..036bf881 --- /dev/null +++ b/data/conf/rspamd/meta_exporter/pipe_rl.php @@ -0,0 +1,38 @@ +<?php +// File size is limited by Nginx site to 10M +// To speed things up, we do not include prerequisites +header('Content-Type: text/plain'); +require_once "vars.inc.php"; +// Do not show errors, we log to using error_log +ini_set('error_reporting', 0); +// Init Redis +$redis = new Redis(); +$redis->connect('redis-mailcow', 6379); + +$raw_data_content = file_get_contents('php://input'); +$raw_data_decoded = json_decode($raw_data_content, true); + +$data['time'] = time(); +$data['rcpt'] = implode(', ', $raw_data_decoded['rcpt']); +$data['from'] = $raw_data_decoded['from']; +$data['user'] = $raw_data_decoded['user']; +$symbol_rl_key = array_search('RATELIMITED', array_column($raw_data_decoded['symbols'], 'name')); +$data['rl_info'] = implode($raw_data_decoded['symbols'][$symbol_rl_key]['options']); +preg_match('/(.+)\((.+)\)/i', $data['rl_info'], $rl_matches); +if (!empty($rl_matches[1]) && !empty($rl_matches[2])) { + $data['rl_name'] = $rl_matches[1]; + $data['rl_hash'] = $rl_matches[2]; +} +else { + $data['rl_name'] = 'err'; + $data['rl_hash'] = 'err'; +} +$data['qid'] = $raw_data_decoded['qid']; +$data['ip'] = $raw_data_decoded['ip']; +$data['message_id'] = $raw_data_decoded['message_id']; +$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']); +$data['header_from'] = implode(', ', $raw_data_decoded['header_from']); + +$redis->lpush('RL_LOG', json_encode($data)); +exit; + diff --git a/data/conf/rspamd/override.d/logging.inc b/data/conf/rspamd/override.d/logging.inc index 23a9f3cf..7765bb34 100644 --- a/data/conf/rspamd/override.d/logging.inc +++ b/data/conf/rspamd/override.d/logging.inc @@ -1,4 +1,5 @@ type = "console"; systemd = false; +level = "silent"; .include "$CONFDIR/logging.inc" .include(try=true; priority=20) "$CONFDIR/override.d/logging.custom.inc" diff --git a/data/conf/rspamd/override.d/ratelimit.conf b/data/conf/rspamd/override.d/ratelimit.conf index 3150d0e5..ccd083d4 100644 --- a/data/conf/rspamd/override.d/ratelimit.conf +++ b/data/conf/rspamd/override.d/ratelimit.conf @@ -1,11 +1,12 @@ rates { # Format: "1 / 1h" or "20 / 1m" etc. - global ratelimits are disabled by default - to = "100 / 1s"; - to_ip = "100 / 1s"; - to_ip_from = "100 / 1s"; + to = "45 / 1m"; + to_ip = "360 / 1m"; + to_ip_from = "180 / 1m"; bounce_to = "100 / 1s"; bounce_to_ip = "100 / 1s"; } whitelisted_rcpts = "postmaster,mailer-daemon"; max_rcpt = 5; custom_keywords = "/etc/rspamd/lua/ratelimit.lua"; +info_symbol = "RATELIMITED"; diff --git a/data/conf/rspamd/override.d/worker-controller.inc b/data/conf/rspamd/override.d/worker-controller.inc index 60338e15..4d4ffe6f 100644 --- a/data/conf/rspamd/override.d/worker-controller.inc +++ b/data/conf/rspamd/override.d/worker-controller.inc @@ -1,7 +1,7 @@ bind_socket = "*:11334"; -count = 2; +count = 1; secure_ip = "127.0.0.1"; secure_ip = "::1"; -bind_socket = "/rspamd-sock/rspamd.sock mode=0666 owner=nobody"; +bind_socket = "/var/lib/rspamd/rspamd.sock mode=0666 owner=nobody"; .include(try=true; priority=10) "$CONFDIR/override.d/worker-controller-password.inc" .include(try=true; priority=20) "$CONFDIR/override.d/worker-controller.custom.inc" diff --git a/data/conf/rspamd/override.d/worker-fuzzy.inc b/data/conf/rspamd/override.d/worker-fuzzy.inc new file mode 100644 index 00000000..09b39c93 --- /dev/null +++ b/data/conf/rspamd/override.d/worker-fuzzy.inc @@ -0,0 +1,12 @@ +# Socket to listen on (UDP and TCP from rspamd 1.3) +bind_socket = "*:11445"; +allow_update = ["127.0.0.1", "::1"]; +# Number of processes to serve this storage (useful for read scaling) +count = 2; +# Backend ("sqlite" or "redis" - default "sqlite") +backend = "redis"; +# Hashes storage time (3 months) +expire = 90d; +# Synchronize updates to the storage each minute +sync = 1min; + diff --git a/data/conf/rspamd/override.d/worker-proxy.inc b/data/conf/rspamd/override.d/worker-proxy.inc index 0df926a7..92527f2b 100644 --- a/data/conf/rspamd/override.d/worker-proxy.inc +++ b/data/conf/rspamd/override.d/worker-proxy.inc @@ -1,6 +1,6 @@ bind_socket = "rspamd:9900"; milter = true; -upstream { +upstream "local" { name = "localhost"; default = true; hosts = "rspamd:11333" diff --git a/data/conf/sogo/custom-sogo.js b/data/conf/sogo/custom-sogo.js new file mode 100644 index 00000000..9ee6daba --- /dev/null +++ b/data/conf/sogo/custom-sogo.js @@ -0,0 +1,7 @@ +// Custom SOGo JS + +// Change the visible font-size in the editor, this does not change the font of a html message by default +CKEDITOR.addCss("body {font-size: 16px !important}"); + +// Enable scayt by default +//CKEDITOR.config.scayt_autoStartup = true; diff --git a/data/conf/sogo/plist_ldap b/data/conf/sogo/plist_ldap new file mode 100644 index 00000000..d585a494 --- /dev/null +++ b/data/conf/sogo/plist_ldap @@ -0,0 +1,28 @@ + <!-- + <example> + <key>canAuthenticate</key> + <string>YES</string> + <key>id</key> + <string>${line}_ldap</string> + <key>isAddressBook</key> + <string>NO</string> + <key>IDFieldName</key> + <string>mail</string> + <key>UIDFieldName</key> + <string>uid</string> + <key>bindFields</key> + <array> + <string>mail</string> + </array> + <key>type</key> + <string>ldap</string> + <key>bindDN</key> + <string>cn=admin,dc=example,dc=local</string> + <key>bindPassword</key> + <string>password</string> + <key>baseDN</key> + <string>ou=People,dc=example,dc=local</string> + <key>hostname</key> + <string>ldap://1.2.3.4:389</string> + </example> + --> diff --git a/data/conf/sogo/sogo-full.svg b/data/conf/sogo/sogo-full.svg new file mode 100644 index 00000000..98ff2fc3 --- /dev/null +++ b/data/conf/sogo/sogo-full.svg @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY st0 "fill:#50BD37;"> +]> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="640px" height="350px" viewBox="78.712 58.488 640 350" style="enable-background:new 78.712 58.488 640 350;" + xml:space="preserve"> +<path style="&st0;" d="M648.541,145.679c-9.947,0-17.009-7.278-17.009-17.048c0-9.777,7.062-17.057,17.009-17.057 + c10.024,0,17.086,7.279,17.086,17.057C665.627,138.401,658.565,145.679,648.541,145.679z M648.511,94.893 + c-19.693,0-33.679,14.4-33.679,33.738c0,19.33,13.985,33.729,33.679,33.729c19.822,0,33.808-14.4,33.808-33.729 + C682.318,109.293,668.333,94.893,648.511,94.893z M648.482,179.843c-29.889,0-51.123-21.868-51.123-51.212 + c0-29.353,21.234-51.209,51.123-51.209c30.082,0,51.307,21.856,51.307,51.209C699.789,157.975,678.564,179.843,648.482,179.843z + M648.442,58.488c-40.929,0-69.995,29.946-69.995,70.143c0,40.189,29.066,70.125,69.995,70.125c41.194,0,70.27-29.937,70.27-70.125 + C718.712,88.434,689.637,58.488,648.442,58.488z M158.166,183.902l-21.018-5.008c-19.131-4.396-28.849-9.413-28.849-23.21 + c0-15.684,15.99-21.965,30.419-21.965c14.667,0,25.382,7.329,31.693,18.737c0.02,0.048,0.051,0.097,0.09,0.157 + c0.127,0.247,0.276,0.484,0.403,0.731l0.03-0.02c1.985,3.002,5.323,5.008,8.919,5.008c6.122,0,10.558-4.425,10.558-10.547 + c0-2.341-0.504-4.82-1.601-6.688c-10.764-18.302-28.513-26.192-48.838-26.192c-27.594,0-54.262,13.797-54.262,44.218 + c0,27.921,27.605,36.079,37.64,38.578l20.069,4.71c15.368,3.763,27.912,8.791,27.912,23.517c0,16.938-17.561,23.943-34.499,23.943 + c-17.245,0-30.015-9.37-38.814-22.37h-0.01c-1.956-3-4.988-4.328-8.702-4.328c-5.984,0-10.805,5.185-10.587,11.162 + c0.098,2.438,0.909,4.637,2.153,6.405c13.787,20.633,33.728,28.41,55.96,28.41c28.543,0,57.085-13.143,57.085-45.132 + C193.918,203.325,178.551,188.613,158.166,183.902z M298.479,250.312c-33.866,0-55.199-25.403-55.199-58.331 + c0-32.939,21.333-58.343,55.199-58.343c34.192,0,55.516,25.403,55.516,58.343C353.996,224.91,332.672,250.312,298.479,250.312z + M298.479,114.823c-45.471,0-77.777,32.93-77.777,77.158c0,44.217,32.306,77.146,77.777,77.146 + c45.786,0,78.093-32.929,78.093-77.146C376.572,147.753,344.266,114.823,298.479,114.823z M518.715,234.312 + c-0.771,0.74-1.549,1.472-2.399,2.175c-1.106,1.014-2.391,2.112-3.854,3.208c-8.829,6.391-19.979,10.094-33.017,10.094 + c-33.876,0-55.198-25.402-55.198-58.332c0-32.939,21.322-58.342,55.198-58.342c34.183,0,55.506,25.403,55.506,58.342 + C534.951,208.653,529.135,223.774,518.715,234.312z M468.097,317.938c2.528,0,5.146-0.168,7.863-0.504 + c5.018-0.631,9.588-0.909,13.729-0.909c19.24,0.109,29.036,5.7,34.943,12.158c5.895,6.499,8.168,15.311,8.158,22.796 + c0.01,3.586-0.555,6.795-1.177,8.721c-2.944,8.93-8.888,15.002-17.996,19.576c-9.035,4.484-21.095,6.777-33.707,6.757 + c-4.514,0-9.105-0.288-13.639-0.831c-8.573-0.987-19.911-4.671-28.13-11.093c-4.138-3.199-6.458-6.991-8.858-11.485 + c-2.379-4.514-2.783-9.748-2.783-16.442v-0.742c0-12.346,4.84-20.544,11.051-26.5c3.07-2.904,5.69-5.064,7.99-6.438 + c0.366-0.218,0.438-0.416,0.755-0.593C452.39,316.014,459.684,317.968,468.097,317.938z M479.445,114.301 + c-45.471,0-77.786,32.929-77.786,77.157c0,29.887,14.765,54.598,38.378,67.489c-0.314,0.314-0.621,0.641-0.916,0.966 + c-6.104,6.687-9.226,15.25-9.236,23.913c-0.008,3.821,0.624,7.741,1.977,11.494c-3.062,1.956-6.717,4.634-10.46,8.147 + c-9.026,8.408-18.734,22.541-19.021,42.097c-0.01,0.454-0.01,0.829-0.01,1.118c-0.01,10.071,2.379,19.157,6.459,26.774 + c6.133,11.466,15.683,19.445,25.539,24.77c9.917,5.334,20.257,8.166,29.273,9.274c5.373,0.643,10.826,0.988,16.268,0.988 + c15.151-0.02,30.261-2.578,43.409-9.019c13.085-6.34,24.333-17.253,29.192-32.562c1.443-4.553,2.212-9.719,2.231-15.428 + c-0.02-11.595-3.349-25.759-13.767-37.452c-10.421-11.734-27.654-19.566-51.288-19.459c-5.138,0-10.606,0.356-16.426,1.078 + c-1.877,0.227-3.596,0.334-5.166,0.334c-7.239-0.048-10.872-2.053-13.036-4.098c-2.133-2.084-3.2-4.839-3.229-8.058 + c-0.01-3.28,1.284-6.727,3.467-9.078c2.231-2.332,5.008-3.91,9.846-3.97c0.436,0,0.9,0.01,1.374,0.05 + c3.101,0.216,6.112,0.325,9.037,0.325c24.188,0.047,42.38-7.448,54.756-17.759c12.415-10.312,18.971-22.854,22.071-32.76l-0.04-0.01 + c3.37-8.899,5.197-18.715,5.197-29.166C557.539,147.229,525.234,114.301,479.445,114.301z"/> +</svg> diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 23fd8240..f9e9e077 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -11,10 +11,11 @@ SOGoDraftsFolderName = "Drafts"; SOGoJunkFolderName= "Junk"; SOGoMailDomain = "sogo.local"; - SOGoEnableEMailAlarms = NO; + SOGoEnableEMailAlarms = YES; SOGoFoldersSendEMailNotifications = YES; SOGoForwardEnabled = YES; - SOGoUIAdditionalJSFiles = (js/theme-blue.js); + SOGoUIAdditionalJSFiles = (js/custom-sogo.js); + SOGoEnablePublicAccess = YES; // Multi-domain setup // Domains are isolated, you can define visibility options here. @@ -25,7 +26,6 @@ // (domain3.tld, domain2.tld) // ); - SOGoIMAPServer = "imap://dovecot:143/?tls=YES"; SOGoSieveServer = "sieve://dovecot:4190/?tls=YES"; SOGoSMTPServer = "postfix:588"; WOPort = "0.0.0.0:20000"; @@ -39,17 +39,17 @@ SxVMemLimit = 384; - SOGoMaximumPingInterval = 354; + SOGoMaximumPingInterval = 3540; - SOGoInternalSyncInterval = 30; - SOGoMaximumSyncInterval = 354; + SOGoInternalSyncInterval = 45; + SOGoMaximumSyncInterval = 3540; // 100 seems to break some Android clients - //SOGoMaximumSyncWindowSize = 100; + //SOGoMaximumSyncWindowSize = 99; // This should do the trick for Outlook 2016 - SOGoMaximumSyncResponseSize = 2048; + SOGoMaximumSyncResponseSize = 512; - WOWatchDogRequestTimeout = 10; + WOWatchDogRequestTimeout = 20; WOListenQueueSize = 300; WONoDetach = YES; diff --git a/data/conf/unbound/unbound.conf b/data/conf/unbound/unbound.conf index b3c77671..e2e3e9eb 100644 --- a/data/conf/unbound/unbound.conf +++ b/data/conf/unbound/unbound.conf @@ -2,28 +2,42 @@ server: verbosity: 1 interface: 0.0.0.0 interface: ::0 - logfile: /dev/stdout + logfile: /dev/console do-ip4: yes do-ip6: yes do-udp: yes do-tcp: yes do-daemonize: no + #access-control: 0.0.0.0/0 allow access-control: 10.0.0.0/8 allow access-control: 172.16.0.0/12 allow access-control: 192.168.0.0/16 allow access-control: fc00::/7 allow access-control: fe80::/10 allow + #access-control: ::0/0 allow directory: "/etc/unbound" username: unbound auto-trust-anchor-file: trusted-key.key - private-address: 10.0.0.0/8 - private-address: 172.16.0.0/12 - private-address: 192.168.0.0/16 - private-address: 169.254.0.0/16 - private-address: fc00::/7 - private-address: fe80::/10 + #private-address: 10.0.0.0/8 + #private-address: 172.16.0.0/12 + #private-address: 192.168.0.0/16 + #private-address: 169.254.0.0/16 + #private-address: fc00::/7 + #private-address: fe80::/10 + # cache-min-ttl needs to be less or equal to cache-max-negative-ttl + cache-min-ttl: 5 + cache-max-negative-ttl: 60 root-hints: "/etc/unbound/root.hints" hide-identity: yes hide-version: yes max-udp-size: 4096 msg-buffer-size: 65552 + +remote-control: + control-enable: yes + control-interface: 127.0.0.1 + control-port: 8953 + server-key-file: "/etc/unbound/unbound_server.key" + server-cert-file: "/etc/unbound/unbound_server.pem" + control-key-file: "/etc/unbound/unbound_control.key" + control-cert-file: "/etc/unbound/unbound_control.pem" diff --git a/data/web/admin.php b/data/web/admin.php index d2734431..6826fec5 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -1,50 +1,50 @@ <?php -require_once("inc/prerequisites.inc.php"); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { -require_once("inc/header.inc.php"); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $tfa_data = get_tfa(); ?> <div class="container"> + <ul class="nav nav-tabs" role="tablist"> <li role="presentation" class="active"><a href="#tab-access" aria-controls="tab-access" role="tab" data-toggle="tab"><?=$lang['admin']['access'];?></a></li> <li role="presentation"><a href="#tab-config" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['configuration'];?></a></li> + <li role="presentation"><a href="#tab-routing" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['routing'];?></a></li> + <li role="presentation"><a href="#tab-sys-mails" aria-controls="tab-sys-mails" role="tab" data-toggle="tab"><?=$lang['admin']['sys_mails'];?></a></li> + <li role="presentation"><a href="#tab-mailq" aria-controls="tab-mailq" role="tab" data-toggle="tab"><?=$lang['admin']['queue_manager'];?></a></li> </ul> + <div class="row"> + <div class="col-md-12"> <div class="tab-content" style="padding-top:20px"> <div role="tabpanel" class="tab-pane active" id="tab-access"> <div class="panel panel-danger"> <div class="panel-heading"><?=$lang['admin']['admin_details'];?></div> <div class="panel-body"> - <form class="form-horizontal" autocapitalize="none" data-id="admin" autocorrect="off" role="form" method="post"> - <?php $admindetails = get_admin_details(); ?> - <div class="form-group"> - <label class="control-label col-sm-3" for="admin_user"><?=$lang['admin']['admin'];?>:</label> - <div class="col-sm-9"> - <input type="text" class="form-control" name="admin_user" id="admin_user" value="<?=htmlspecialchars($admindetails['username']);?>" required> - ↳ <kbd>a-z A-Z - _ .</kbd> - </div> + <div class="table-responsive"> + <table class="table table-striped table-condensed" id="adminstable"></table> + </div> + <div class="mass-actions-admin"> + <div class="btn-group"> + <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="admins" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> + <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> + <ul class="dropdown-menu"> + <li><a data-action="edit_selected" data-id="admins" data-api-url='edit/admin' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="admins" data-api-url='edit/admin' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li role="separator" class="divider"></li> + <li><a data-action="edit_selected" data-id="admins" data-api-url='edit/admin' data-api-attr='{"disable_tfa":"1"}' href="#"><?=$lang['tfa']['disable_tfa'];?></a></li> + <li role="separator" class="divider"></li> + <li><a data-action="delete_selected" data-id="admins" data-api-url='delete/admin' href="#"><?=$lang['mailbox']['remove'];?></a></li> + </ul> + <a class="btn btn-sm btn-success" data-id="add_admin" data-toggle="modal" data-target="#addAdminModal" href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add_admin'];?></a> </div> - <div class="form-group"> - <label class="control-label col-sm-3" for="admin_pass"><?=$lang['admin']['password'];?>:</label> - <div class="col-sm-9"> - <input type="password" class="form-control" name="admin_pass" id="admin_pass" placeholder="<?=$lang['admin']['unchanged_if_empty'];?>"> - </div> - </div> - <div class="form-group"> - <label class="control-label col-sm-3" for="admin_pass2"><?=$lang['admin']['password_repeat'];?>:</label> - <div class="col-sm-9"> - <input type="password" class="form-control" name="admin_pass2" id="admin_pass2"> - </div> - </div> - <div class="form-group"> - <div class="col-sm-offset-3 col-sm-9"> - <button class="btn btn-default" id="edit_selected" data-id="admin" data-item="admin" data-api-url='edit/self' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> - </div> - </div> - </form> - <hr> + </div> + <legend style="margin-top:20px"> + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="margin-bottom: -5px;"> + <path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"/> + </svg> <?=$lang['tfa']['tfa'];?></legend> <div class="row"> <div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?>:</div> <div class="col-sm-9 col-xs-7"> @@ -68,7 +68,7 @@ $tfa_data = get_tfa(); <div class="row"> <div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?>:</div> <div class="col-sm-9 col-xs-7"> - <select data-width="auto" id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>"> + <select data-width="fit" id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>"> <option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option> <option value="u2f"><?=$lang['tfa']['u2f'];?></option> <option value="totp"><?=$lang['tfa']['totp'];?></option> @@ -76,29 +76,30 @@ $tfa_data = get_tfa(); </select> </div> </div> - </div> - </div> - - <div class="hidden panel panel-primary"> - <div class="panel-heading">API</div> - <div class="panel-body"> + <legend data-target="#api" style="margin-top:40px;cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse"> + <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> API (experimental, work in progress) + </legend> + <?php + $api = admin_api('get'); + ?> + <div id="api" class="collapse"> <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post"> <div class="form-group"> <label class="control-label col-sm-3" for="allow_from"><?=$lang['admin']['api_allow_from'];?>:</label> <div class="col-sm-9"> - <textarea class="form-control" rows="5" name="allow_from" id="allow_from" required><?=htmlspecialchars($admindetails['allow_from']);?></textarea> + <textarea class="form-control" rows="5" name="allow_from" id="allow_from" required><?=htmlspecialchars($api['allow_from']);?></textarea> </div> </div> <div class="form-group"> <label class="control-label col-sm-3" for="admin_api_key"><?=$lang['admin']['api_key'];?>:</label> <div class="col-sm-9"> - <input type="text" class="form-control" placeholder="-" value="<?=htmlspecialchars($admindetails['api_key']);?>" readonly> + <input type="text" class="form-control" placeholder="-" value="<?=htmlspecialchars($api['api_key']);?>" readonly> </div> </div> <div class="form-group"> <div class="col-sm-offset-3 col-sm-9"> <label> - <input type="checkbox" name="active" <?=($admindetails['api_active'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['activate_api'];?> + <input type="checkbox" name="active" <?=($api['active'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['activate_api'];?> </label> </div> </div> @@ -111,6 +112,7 @@ $tfa_data = get_tfa(); </div> </div> </form> + </div> </div> </div> @@ -118,19 +120,19 @@ $tfa_data = get_tfa(); <div class="panel-heading"><?=$lang['admin']['domain_admins'];?></div> <div class="panel-body"> <div class="table-responsive"> - <table class="table table-striped" id="domainadminstable"></table> + <table class="table table-striped table-condensed" id="domainadminstable"></table> </div> <div class="mass-actions-admin"> <div class="btn-group"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="domain_admins" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"disable_tfa":"1"}' href="#"><?=$lang['tfa']['disable_tfa'];?></a></li> + <li><a data-action="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"disable_tfa":"1"}' href="#"><?=$lang['tfa']['disable_tfa'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="domain_admins" data-api-url='delete/domain-admin' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="domain_admins" data-api-url='delete/domain-admin' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> <a class="btn btn-sm btn-success" data-id="add_domain_admin" data-toggle="modal" data-target="#addDomainAdminModal" href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add_domain_admin'];?></a> </div> @@ -156,13 +158,13 @@ $tfa_data = get_tfa(); <div class="form-group"> <label class="control-label col-sm-3" for="rspamd_ui_pass"><?=$lang['admin']['password'];?>:</label> <div class="col-sm-9"> - <input type="password" class="form-control" name="rspamd_ui_pass" id="rspamd_ui_pass" required> + <input type="password" class="form-control" name="rspamd_ui_pass" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3" for="rspamd_ui_pass2"><?=$lang['admin']['password_repeat'];?>:</label> <div class="col-sm-9"> - <input type="password" class="form-control" name="rspamd_ui_pass2" id="rspamd_ui_pass2" required> + <input type="password" class="form-control" name="rspamd_ui_pass2" required> </div> </div> <div class="form-group"> @@ -180,6 +182,99 @@ $tfa_data = get_tfa(); </div> </div> + <div role="tabpanel" class="tab-pane" id="tab-routing"> + <div class="panel panel-default"> + <div class="panel-heading"><?=$lang['admin']['relayhosts'];?></div> + <div class="panel-body"> + <p style="margin-bottom:40px"><?=$lang['admin']['relayhosts_hint'];?></p> + <div class="table-responsive"> + <table class="table table-striped table-condensed" id="relayhoststable"></table> + </div> + <div class="mass-actions-admin"> + <div class="btn-group btn-group-sm"> + <button type="button" id="toggle_multi_select_all" data-id="rlyhosts" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button> + <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> + <ul class="dropdown-menu"> + <li><a data-action="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li role="separator" class="divider"></li> + <li><a data-action="delete_selected" data-id="rlyhosts" data-api-url='delete/relayhost' href="#"><?=$lang['admin']['remove'];?></a></li> + </ul> + </div> + </div> + <legend><?=$lang['admin']['add_relayhost'];?></legend> + <p class="help-block"><?=$lang['admin']['add_relayhost_hint'];?></p> + <div class="row"> + <div class="col-md-6"> + <form class="form" data-id="rlyhost" role="form" method="post"> + <div class="form-group"> + <label for="hostname"><?=$lang['admin']['host'];?></label> + <input class="form-control input-sm" name="hostname" placeholder='host:25, host, [host]:25, [0.0.0.0]:25' required> + </div> + <div class="form-group"> + <label for="username"><?=$lang['admin']['username'];?></label> + <input class="form-control input-sm" name="username"> + </div> + <div class="form-group"> + <label for="password"><?=$lang['admin']['password'];?></label> + <input class="form-control input-sm" name="password"> + </div> + <button class="btn btn-default" data-action="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button> + </form> + </div> + </div> + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading"><?=$lang['admin']['transport_maps'];?></div> + <div class="panel-body"> + <p style="margin-bottom:40px"><?=$lang['admin']['transports_hint'];?></p> + <div class="table-responsive"> + <table class="table table-striped table-condensed" id="transportstable"></table> + </div> + <div class="mass-actions-admin"> + <div class="btn-group btn-group-sm"> + <button type="button" id="toggle_multi_select_all" data-id="transports" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button> + <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> + <ul class="dropdown-menu"> + <li><a data-action="edit_selected" data-id="transports" data-api-url='edit/transport' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="transports" data-api-url='edit/transport' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li role="separator" class="divider"></li> + <li><a data-action="delete_selected" data-id="transports" data-api-url='delete/transport' href="#"><?=$lang['admin']['remove'];?></a></li> + </ul> + </div> + </div> + <legend><?=$lang['admin']['add_transport'];?></legend> + <p class="help-block"><?=$lang['admin']['add_transports_hint'];?></p> + <div class="row"> + <div class="col-md-6"> + <form class="form" data-id="transport" role="form" method="post"> + <div class="form-group"> + <label for="destination"><?=$lang['admin']['destination'];?></label> + <input class="form-control input-sm" name="destination" placeholder='example.org, .example.org, *, box@example.org' required> + </div> + <div class="form-group"> + <label for="nexthop"><?=$lang['admin']['nexthop'];?></label> + <input class="form-control input-sm" name="nexthop" placeholder='host:25, host, [host]:25, [0.0.0.0]:25' required> + </div> + <div class="form-group"> + <label for="username"><?=$lang['admin']['username'];?></label> + <input class="form-control input-sm" name="username"> + </div> + <div class="form-group"> + <label for="password"><?=$lang['admin']['password'];?></label> + <input class="form-control" name="password"> + </div> + <p class="help-block"><?=$lang['admin']['credentials_transport_warning'];?></p> + <button class="btn btn-default" data-action="add_item" data-id="transport" data-api-url='add/transport' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button> + </form> + </div> + </div> + </div> + </div> + </div> + <div role="tabpanel" class="tab-pane" id="tab-config"> <div class="row"> <div id="sidebar-admin" class="col-sm-2 hidden-xs"> @@ -187,9 +282,9 @@ $tfa_data = get_tfa(); <a href="#dkim" class="list-group-item"><?=$lang['admin']['dkim_keys'];?></a> <a href="#fwdhosts" class="list-group-item"><?=$lang['admin']['forwarding_hosts'];?></a> <a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a> - <a href="#relayhosts" class="list-group-item">Relayhosts</a> <a href="#quarantine" class="list-group-item"><?=$lang['admin']['quarantine'];?></a> - <a href="#rsettings" class="list-group-item">Rspamd settings map</a> + <a href="#quota" class="list-group-item"><?=$lang['admin']['quota_notifications'];?></a> + <a href="#rsettings" class="list-group-item"><?=$lang['admin']['rspamd_settings_map'];?></a> <a href="#customize" class="list-group-item"><?=$lang['admin']['customize'];?></a> <a href="#top" class="list-group-item" style="border-top:1px dashed #dadada">โธ <?=$lang['admin']['to_top'];?></a> </div> @@ -202,7 +297,7 @@ $tfa_data = get_tfa(); <div class="mass-actions-admin"> <div class="btn-group btn-group-sm"> <button type="button" id="toggle_multi_select_all" data-id="dkim" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button> - <button type="button" id="delete_selected" name="delete_selected" data-id="dkim" data-api-url="delete/dkim" class="btn btn-danger"><?=$lang['admin']['remove'];?></button> + <button type="button" data-action="delete_selected" name="delete_selected" data-id="dkim" data-api-url="delete/dkim" class="btn btn-danger"><?=$lang['admin']['remove'];?></button> </div> </div> <?php @@ -214,15 +309,15 @@ $tfa_data = get_tfa(); <div class="row"> <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" /></div> <div class="col-md-3"> - <p>Domain: <strong><?=htmlspecialchars($domain);?></strong> + <p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong> <p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p> - <p class="dkim-label"><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p> + <p class="dkim-label"><span class="label label-primary"><?=$lang['admin']['dkim_domains_selector'];?> '<?=$dkim['dkim_selector'];?>'</span></p> <p class="dkim-label"><span class="label label-info"><?=$dkim['length'];?> bit</span></p> </p> </div> <div class="col-md-8"> <pre><?=$dkim['dkim_txt'];?></pre> - <p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>โช Private key</small></p> + <p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>โช <?=$lang['admin']['dkim_private_key'];?></small></p> </div> <hr class="visible-xs visible-sm"> </div> @@ -233,7 +328,7 @@ $tfa_data = get_tfa(); <div class="row"> <div class="col-md-1"><input class="dkim_missing" type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" disabled /></div> <div class="col-md-3"> - <p>Domain: <strong><?=htmlspecialchars($domain);?></strong><br /><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p> + <p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong><br /><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p> </div> <div class="col-md-8"><pre>-</pre></div> <hr class="visible-xs visible-sm"> @@ -284,7 +379,7 @@ $tfa_data = get_tfa(); <div class="row"> <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$blind;?>" /></div> <div class="col-md-3"> - <p>Domain: <strong><?=htmlspecialchars($blind);?></strong> + <p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($blind);?></strong> <p class="dkim-label"><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p> <p class="dkim-label"><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p> <p class="dkim-label"><span class="label label-info"><?=$dkim['length'];?> bit</span></p> @@ -309,8 +404,8 @@ $tfa_data = get_tfa(); <small>โช <a href="#" id="dkim_missing_keys"><?=$lang['admin']['dkim_domains_wo_keys'];?></a></small> </div> <div class="form-group"> - <label for="domain">Selector</label> - <input class="form-control input-sm" id="dkim_selector" name="dkim_selector" value="dkim" required> + <label for="domain"><?=$lang['admin']['dkim_domains_selector'];?></label> + <input class="form-control input-sm" name="dkim_selector" value="dkim" required> </div> <div class="form-group"> <select data-width="200px" data-style="btn btn-default btn-sm" class="form-control" id="key_size" name="key_size" title="<?=$lang['admin']['dkim_key_length'];?>" required> @@ -318,32 +413,32 @@ $tfa_data = get_tfa(); <option data-subtext="bits">2048</option> </select> </div> - <button class="btn btn-sm btn-default" id="add_item" data-id="dkim" data-api-url='add/dkim' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button> + <button class="btn btn-sm btn-default" data-action="add_item" data-id="dkim" data-api-url='add/dkim' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button> </form> - <legend data-target="#import_dkim" style="margin-top:40px;cursor:pointer" id="import_dkim_legend" unselectable="on" data-toggle="collapse"> - <span id="import_dkim_arrow" style="font-size:12px" class="rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['import_private_key'];?> + <legend data-target="#import_dkim" style="margin-top:40px;cursor:pointer" class="arrow-toggle"" unselectable="on" data-toggle="collapse"> + <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['import_private_key'];?> </legend> <div id="import_dkim" class="collapse"> <form class="form" data-id="dkim_import" role="form" method="post"> <div class="form-group"> - <label for="domain">Domain:</label> - <input class="form-control input-sm" id="domain" name="domain" placeholder="example.org" required> + <label for="domain"><?=$lang['admin']['domain'];?>:</label> + <input class="form-control input-sm" name="domain" placeholder="example.org" required> </div> <div class="form-group"> - <label for="domain">Selector:</label> - <input class="form-control input-sm" id="dkim_selector" name="dkim_selector" value="dkim" required> + <label for="domain"><?=$lang['admin']['dkim_domains_selector'];?>:</label> + <input class="form-control input-sm" name="dkim_selector" value="dkim" required> </div> <div class="form-group"> - <label for="private_key_file"><?=$lang['admin']['private_key'];?>:</label> + <label for="private_key_file"><?=$lang['admin']['private_key'];?>: (RSA PKCS#8)</label> <textarea class="form-control input-sm" rows="10" name="private_key_file" id="private_key_file" required placeholder="-----BEGIN RSA KEY-----"></textarea> </div> - <button class="btn btn-sm btn-default" id="add_item" data-id="dkim_import" data-api-url='add/dkim_import' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['import'];?></button> + <button class="btn btn-sm btn-default" data-action="add_item" data-id="dkim_import" data-api-url='add/dkim_import' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['import'];?></button> </form> </div> - <legend data-target="#duplicate_dkim" style="margin-top:40px;cursor:pointer" id="duplicate_dkim_legend" unselectable="on" data-toggle="collapse"> - <span id="duplicate_dkim_arrow" style="font-size:12px" class="rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['duplicate_dkim'];?> + <legend data-target="#duplicate_dkim" style="margin-top:40px;cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse"> + <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['duplicate_dkim'];?> </legend> <div id="duplicate_dkim" class="collapse"> <form class="form-horizontal" data-id="dkim_duplicate" role="form" method="post"> @@ -384,7 +479,7 @@ $tfa_data = get_tfa(); </select> </div> </div> - <button class="btn btn-sm btn-default" id="add_item" data-id="dkim_duplicate" data-api-url='add/dkim_duplicate' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-duplicate"></span> <?=$lang['admin']['duplicate'];?></button> + <button class="btn btn-sm btn-default" data-action="add_item" data-id="dkim_duplicate" data-api-url='add/dkim_duplicate' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-duplicate"></span> <?=$lang['admin']['duplicate'];?></button> </form> </div> @@ -404,10 +499,10 @@ $tfa_data = get_tfa(); <button type="button" id="toggle_multi_select_all" data-id="fwdhosts" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="fwdhosts" data-api-url='edit/fwdhost' data-api-attr='{"keep_spam":"0"}' href="#">Enable spam filter</a></li> - <li><a id="edit_selected" data-id="fwdhosts" data-api-url='edit/fwdhost' data-api-attr='{"keep_spam":"1"}' href="#">Disable spam filter</a></li> + <li><a data-action="edit_selected" data-id="fwdhosts" data-api-url='edit/fwdhost' data-api-attr='{"keep_spam":"0"}' href="#">Enable spam filter</a></li> + <li><a data-action="edit_selected" data-id="fwdhosts" data-api-url='edit/fwdhost' data-api-attr='{"keep_spam":"1"}' href="#">Disable spam filter</a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="fwdhosts" data-api-url='delete/fwdhost' href="#"><?=$lang['admin']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="fwdhosts" data-api-url='delete/fwdhost' href="#"><?=$lang['admin']['remove'];?></a></li> </ul> </div> </div> @@ -416,7 +511,7 @@ $tfa_data = get_tfa(); <form class="form" data-id="fwdhost" role="form" method="post"> <div class="form-group"> <label for="hostname"><?=$lang['admin']['host'];?></label> - <input class="form-control" id="hostname" name="hostname" placeholder="example.org" required> + <input class="form-control" name="hostname" placeholder="example.org" required> </div> <div class="form-group"> <select data-width="200px" class="form-control" id="filter_spam" name="filter_spam" title="<?=$lang['user']['spamfilter'];?>" required> @@ -424,7 +519,7 @@ $tfa_data = get_tfa(); <option value="0"><?=$lang['admin']['inactive'];?></option> </select> </div> - <button class="btn btn-default" id="add_item" data-id="fwdhost" data-api-url='add/fwdhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="fwdhost" data-api-url='add/fwdhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button> </form> </div> </div> @@ -439,41 +534,41 @@ $tfa_data = get_tfa(); <form class="form" data-id="f2b" role="form" method="post"> <div class="form-group"> <label for="ban_time"><?=$lang['admin']['f2b_ban_time'];?>:</label> - <input type="number" class="form-control" id="ban_time" name="ban_time" value="<?=$f2b_data['ban_time'];?>" required> + <input type="number" class="form-control" name="ban_time" value="<?=$f2b_data['ban_time'];?>" required> </div> <div class="form-group"> <label for="max_attempts"><?=$lang['admin']['f2b_max_attempts'];?>:</label> - <input type="number" class="form-control" id="max_attempts" name="max_attempts" value="<?=$f2b_data['max_attempts'];?>" required> + <input type="number" class="form-control" name="max_attempts" value="<?=$f2b_data['max_attempts'];?>" required> </div> <div class="form-group"> <label for="retry_window"><?=$lang['admin']['f2b_retry_window'];?>:</label> - <input type="number" class="form-control" id="retry_window" name="retry_window" value="<?=$f2b_data['retry_window'];?>" required> + <input type="number" class="form-control" name="retry_window" value="<?=$f2b_data['retry_window'];?>" required> </div> <div class="form-group"> <label for="netban_ipv4"><?=$lang['admin']['f2b_netban_ipv4'];?>:</label> <div class="input-group"> <span class="input-group-addon">/</span> - <input type="number" class="form-control" id="netban_ipv4" name="netban_ipv4" value="<?=$f2b_data['netban_ipv4'];?>" required> + <input type="number" class="form-control" name="netban_ipv4" value="<?=$f2b_data['netban_ipv4'];?>" required> </div> </div> <div class="form-group"> <label for="netban_ipv6"><?=$lang['admin']['f2b_netban_ipv6'];?>:</label> <div class="input-group"> <span class="input-group-addon">/</span> - <input type="number" class="form-control" id="netban_ipv6" name="netban_ipv6" value="<?=$f2b_data['netban_ipv6'];?>" required> + <input type="number" class="form-control" name="netban_ipv6" value="<?=$f2b_data['netban_ipv6'];?>" required> </div> </div> <p class="help-block"><?=$lang['admin']['f2b_list_info'];?></p> <div class="form-group"> <label for="whitelist"><?=$lang['admin']['f2b_whitelist'];?>:</label> - <textarea class="form-control" id="whitelist" name="whitelist" rows="5"><?=$f2b_data['whitelist'];?></textarea> + <textarea class="form-control" name="whitelist" rows="5"><?=$f2b_data['whitelist'];?></textarea> </div> <div class="form-group"> <label for="blacklist"><?=$lang['admin']['f2b_blacklist'];?>:</label> - <textarea class="form-control" id="blacklist" name="blacklist" rows="5"><?=$f2b_data['blacklist'];?></textarea> + <textarea class="form-control" name="blacklist" rows="5"><?=$f2b_data['blacklist'];?></textarea> </div> <div class="btn-group"> - <button class="btn btn-default" id="edit_selected" data-item="self" data-id="f2b" data-api-url='edit/fail2ban' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> + <button class="btn btn-default" data-action="edit_selected" data-item="self" data-id="f2b" data-api-url='edit/fail2ban' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> <a href="#" role="button" class="btn btn-default" data-toggle="modal" data-container="netfilter-mailcow" data-target="#RestartContainer"><span class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_netfilter']; ?></a> </div> </form> @@ -485,73 +580,37 @@ $tfa_data = get_tfa(); <i><?=$lang['admin']['no_active_bans'];?></i> <?php endif; - foreach ($f2b_data['active_bans'] as $active_bans): - ?> - <p><span class="label label-info" style="padding:4px;font-size:85%;"><span class="glyphicon glyphicon-filter"></span> <?=$active_bans['network'];?> (<?=$active_bans['banned_until'];?>) - - <?php - if ($active_bans['queued_for_unban'] == 0): + if (!empty($f2b_data['active_bans'])): + foreach ($f2b_data['active_bans'] as $active_bans): ?> - <a id="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"unban"}' href="#">[<?=$lang['admin']['queue_unban'];?>]</a> - <a id="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"whitelist"}' href="#">[whitelist]</a> - <a id="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"blacklist"}' href="#">[blacklist]</a> + <p><span class="label label-info" style="padding:4px;font-size:85%;"><span class="glyphicon glyphicon-filter"></span> <?=$active_bans['network'];?> (<?=$active_bans['banned_until'];?>) - + <?php + if ($active_bans['queued_for_unban'] == 0): + ?> + <a data-action="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"unban"}' href="#">[<?=$lang['admin']['queue_unban'];?>]</a> + <a data-action="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"whitelist"}' href="#">[whitelist]</a> + <a data-action="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"blacklist"}' href="#">[blacklist]</a> + <?php + else: + ?> + <i><?=$lang['admin']['unban_pending'];?></i> + <?php + endif; + ?> + </span></p> <?php - else: + endforeach; + endif; + if (!empty($f2b_data['perm_bans'])): + foreach ($f2b_data['perm_bans'] as $perm_bans): ?> - <i><?=$lang['admin']['unban_pending'];?></i> + <p> + <span class="label label-danger" style="padding:4px;font-size:85%;"><span class="glyphicon glyphicon-filter"></span> <?=$perm_bans?></span> + </p> <?php - endif; - ?> - </span></p> - <?php - endforeach; - foreach ($f2b_data['perm_bans'] as $perm_bans): + endforeach; + endif; ?> - <p> - <span class="label label-danger" style="padding:4px;font-size:85%;"><span class="glyphicon glyphicon-filter"></span> <?=$perm_bans?></span> - </p> - <?php - endforeach; - ?> - </div> - </div> - - <span class="anchor" id="relayhosts"></span> - <div class="panel panel-default"> - <div class="panel-heading">Relayhosts</div> - <div class="panel-body"> - <p style="margin-bottom:40px"><?=$lang['admin']['relayhosts_hint'];?></p> - <div class="table-responsive"> - <table class="table table-striped table-condensed" id="relayhoststable"></table> - </div> - <div class="mass-actions-admin"> - <div class="btn-group btn-group-sm"> - <button type="button" id="toggle_multi_select_all" data-id="rlyhosts" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button> - <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> - <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> - <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="rlyhosts" data-api-url='delete/relayhost' href="#"><?=$lang['admin']['remove'];?></a></li> - </ul> - </div> - </div> - <legend><?=$lang['admin']['add_relayhost'];?></legend> - <p class="help-block"><?=$lang['admin']['add_relayhost_add_hint'];?></p> - <form class="form" data-id="rlyhost" role="form" method="post"> - <div class="form-group"> - <label for="hostname"><?=$lang['admin']['host'];?></label> - <input class="form-control" id="hostname" name="hostname" required> - </div> - <div class="form-group"> - <label for="hostname"><?=$lang['admin']['username'];?></label> - <input class="form-control" id="username" name="username"> - </div> - <div class="form-group"> - <label for="hostname"><?=$lang['admin']['password'];?></label> - <input class="form-control" id="password" name="password"> - </div> - <button class="btn btn-default" id="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button> - </form> </div> </div> @@ -559,46 +618,135 @@ $tfa_data = get_tfa(); <div class="panel panel-default"> <div class="panel-heading"><?=$lang['admin']['quarantine'];?></div> <div class="panel-body"> - <?php $q_data = quarantine('settings'); ?> + <?php $q_data = quarantine('settings');?> <form class="form" data-id="quarantine" role="form" method="post"> <div class="row"> <div class="col-sm-6"> <div class="form-group"> <label for="retention_size"><?=$lang['admin']['quarantine_retention_size'];?></label> - <input type="number" class="form-control" id="retention_size" name="retention_size" value="<?=$q_data['retention_size'];?>" placeholder="0" required> + <input type="number" class="form-control" name="retention_size" value="<?=$q_data['retention_size'];?>" placeholder="0" required> </div> </div> <div class="col-sm-6"> <div class="form-group"> <label for="max_size"><?=$lang['admin']['quarantine_max_size'];?></label> - <input type="number" class="form-control" id="max_size" name="max_size" value="<?=$q_data['max_size'];?>" placeholder="0" required> + <input type="number" class="form-control" name="max_size" value="<?=$q_data['max_size'];?>" placeholder="0" required> </div> </div> </div> - <div class="form-group"> - <label for="exclude_domains"><?=$lang['admin']['quarantine_exclude_domains'];?></label><br /> - <select data-width="100%" id="exclude_domains" name="exclude_domains" class="selectpicker" title="<?=$lang['tfa']['select'];?>" multiple> - <?php - foreach (array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')) as $domain): - ?> - <option <?=(in_array($domain, $q_data['exclude_domains'])) ? 'selected' : null;?>><?=htmlspecialchars($domain);?></option> - <?php - endforeach; - ?> - </select> + <div class="row"> + <div class="col-sm-6"> + <div class="form-group"> + <label for="sender"><?=$lang['admin']['quarantine_notification_sender'];?>:</label> + <input type="text" class="form-control" name="sender" value="<?=$q_data['sender'];?>" placeholder="quarantine@localhost"> + </div> + </div> + <div class="col-sm-6"> + <div class="form-group"> + <label for="subject"><?=$lang['admin']['quarantine_notification_subject'];?>:</label> + <input type="text" class="form-control" name="subject" value="<?=$q_data['subject'];?>" placeholder="Spam Quarantine Notification"> + </div> + </div> </div> - <button class="btn btn-default" id="edit_selected" data-item="self" data-id="quarantine" data-api-url='edit/quarantine' data-api-attr='{"action":"settings"}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> + <div class="row"> + <div class="col-sm-12"> + <legend data-target="#quarantine_template" style="cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse"> + <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['quarantine_notification_html'];?> + </legend> + <div id="quarantine_template" class="collapse" > + <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" name="html_tmpl"><?=$q_data['html_tmpl'];?></textarea> + </div> + </div> + </div> + <div class="row"> + <div class="col-sm-6"> + <div class="form-group"> + <label for="release_format"><?=$lang['admin']['quarantine_release_format'];?>:</label> + <select data-width="100%" name="release_format" class="selectpicker" title="<?=$lang['tfa']['select'];?>"> + <option <?=($q_data['release_format'] == 'raw') ? 'selected' : null;?> value="raw"><?=$lang['admin']['quarantine_release_format_raw'];?></option> + <option <?=($q_data['release_format'] == 'attachment') ? 'selected' : null;?> value="attachment"><?=$lang['admin']['quarantine_release_format_att'];?></option> + </select> + </div> + </div> + <div class="col-sm-6"> + <div class="form-group"> + <label for="exclude_domains"><?=$lang['admin']['quarantine_exclude_domains'];?>:</label><br /> + <select data-width="100%" name="exclude_domains" class="selectpicker" title="<?=$lang['tfa']['select'];?>" multiple> + <?php + foreach (array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')) as $domain): + ?> + <option <?=(in_array($domain, $q_data['exclude_domains'])) ? 'selected' : null;?>><?=htmlspecialchars($domain);?></option> + <?php + endforeach; + ?> + </select> + </div> + </div> + </div> + <button class="btn btn-default" data-action="edit_selected" data-item="self" data-id="quarantine" data-api-url='edit/quarantine' data-api-attr='{"action":"settings"}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> </form> </div> </div> + <span class="anchor" id="quota"></span> + <div class="panel panel-default"> + <div class="panel-heading"><?=$lang['admin']['quota_notifications'];?></div> + <div class="panel-body"> + <p><?=$lang['admin']['quota_notifications_info'];?></p> + <?php $qw_data = quota_notification('get');?> + <form class="form" role="form" data-id="quota_notification" method="post"> + <div class="row"> + <div class="col-sm-6"> + <div class="form-group"> + <label for="sender"><?=$lang['admin']['quarantine_notification_sender'];?>:</label> + <input type="text" class="form-control" name="sender" value="<?=$qw_data['sender'];?>" placeholder="quota-warning@localhost"> + </div> + </div> + <div class="col-sm-6"> + <div class="form-group"> + <label for="subject"><?=$lang['admin']['quarantine_notification_subject'];?>:</label> + <input type="text" class="form-control" name="subject" value="<?=$qw_data['subject'];?>" placeholder="Quota warning"> + </div> + </div> + </div> + <div class="row"> + <div class="col-sm-12"> + <legend data-target="#quota_template" style="cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse"> + <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['quarantine_notification_html'];?> + </legend> + <div id="quota_template" class="collapse" > + <!-- <small><?=$lang['admin']['quota_notifications_vars'];?></small><br><br>--> + <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code collapse in" rows="20" name="html_tmpl"><?=$qw_data['html_tmpl'];?></textarea> + </div> + </div> + </div> + <div class="row"> + <div class="col-sm-10"> + <div class="form-group"> + <br> + <a type="button" class="btn btn-sm btn-success" data-action="edit_selected" + data-item="quota_notification" + data-id="quota_notification" + data-api-url='edit/quota_notification' + data-api-attr='{}'><?=$lang['user']['save_changes'];?></a> + </div> + </div> + </div> + </form> + </div> + </div> + <span class="anchor" id="rsettings"></span> <div class="panel panel-default"> - <div class="panel-heading">Rspamd settings map</div> + <div class="panel-heading"><?=$lang['admin']['rspamd_settings_map'];?></div> <div class="panel-body"> - <legend>Active settings map</legend> - <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="settings_map" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea> - <hr> + <legend data-target="#active_settings_map" style="cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse"> + <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['active_rspamd_settings_map'];?> + </legend> + <div id="active_settings_map" class="collapse" > + <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea> + </div> + <br> <?php $rsettings = rsettings('get'); ?> <form class="form" data-id="rsettings" role="form" method="post"> <div class="row"> @@ -649,19 +797,19 @@ $tfa_data = get_tfa(); <input type="hidden" name="active" value="0"> <div class="form-group"> <label for="desc"><?=$lang['admin']['rsetting_desc'];?>:</label> - <input type="text" class="form-control" id="desc" name="desc" value="<?=$rsetting_details['desc'];?>"> + <input type="text" class="form-control" name="desc" value="<?=$rsetting_details['desc'];?>"> </div> <div class="form-group"> <label for="content"><?=$lang['admin']['rsetting_content'];?>:</label> - <textarea class="form-control" id="content" name="content" rows="10"><?=$rsetting_details['content'];?></textarea> + <textarea class="form-control" name="content" rows="10"><?=$rsetting_details['content'];?></textarea> </div> <div class="form-group"> <label> <input type="checkbox" name="active" value="1" <?=($rsetting_details['active_int'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['active'];?> </label> </div> - <button class="btn btn-default" id="edit_selected" data-item="<?=$rsetting_details['id'];?>" data-id="rsettings" data-api-url='edit/rsetting' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> - <button class="btn btn-danger" id="delete_selected" data-item="<?=$rsetting_details['id'];?>" data-id="rsettings" data-api-url="delete/rsetting" data-api-attr='{}' href="#"><?=$lang['admin']['remove'];?></button> + <button class="btn btn-default" data-action="edit_selected" data-item="<?=$rsetting_details['id'];?>" data-id="rsettings" data-api-url='edit/rsetting' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> + <button class="btn btn-danger" data-action="delete_selected" data-item="<?=$rsetting_details['id'];?>" data-id="rsettings" data-api-url="delete/rsetting" data-api-attr='{}' href="#"><?=$lang['admin']['remove'];?></button> </form> </div> <?php @@ -744,40 +892,173 @@ $tfa_data = get_tfa(); ?> </table> <p><div class="btn-group"> - <button class="btn btn-sm btn-default" id="edit_selected" data-item="admin" data-id="app_links" data-reload="no" data-api-url='edit/app_links' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> + <button class="btn btn-sm btn-default" data-action="edit_selected" data-item="admin" data-id="app_links" data-reload="no" data-api-url='edit/app_links' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> <button class="btn btn-sm btn-default" type="button" id="add_app_link_row"><?=$lang['admin']['add_row'];?></button> </div></p> </form> - <legend><?=$lang['admin']['ui_texts'];?></legend> + <legend data-target="#ui_texts" style="cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse"> + <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['ui_texts'];?> + </legend> + <div id="ui_texts" class="collapse" > <?php $ui_texts = customize('get', 'ui_texts'); ?> - <form class="form" data-id="uitexts" role="form" method="post"> - <div class="form-group"> - <label for="title_name"><?=$lang['admin']['title_name'];?>:</label> - <input type="text" class="form-control" id="title_name" name="title_name" placeholder="mailcow UI" value="<?=$ui_texts['title_name'];?>"> - </div> - <div class="form-group"> - <label for="main_name"><?=$lang['admin']['main_name'];?>:</label> - <input type="text" class="form-control" id="main_name" name="main_name" placeholder="mailcow UI" value="<?=$ui_texts['main_name'];?>"> - </div> - <div class="form-group"> - <label for="apps_name"><?=$lang['admin']['apps_name'];?>:</label> - <input type="text" class="form-control" id="apps_name" name="apps_name" placeholder="mailcow Apps" value="<?=$ui_texts['apps_name'];?>"> - </div> - <div class="form-group"> - <label for="help_text"><?=$lang['admin']['help_text'];?>:</label> - <textarea class="form-control" id="help_text" name="help_text" rows="7"><?=$ui_texts['help_text'];?></textarea> - </div> - <button class="btn btn-default" id="edit_selected" data-item="ui" data-id="uitexts" data-api-url='edit/ui_texts' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> - </form> + <form class="form" data-id="uitexts" role="form" method="post"> + <div class="form-group"> + <label for="title_name"><?=$lang['admin']['title_name'];?>:</label> + <input type="text" class="form-control" name="title_name" placeholder="mailcow UI" value="<?=$ui_texts['title_name'];?>"> + </div> + <div class="form-group"> + <label for="main_name"><?=$lang['admin']['main_name'];?>:</label> + <input type="text" class="form-control" name="main_name" placeholder="mailcow UI" value="<?=$ui_texts['main_name'];?>"> + </div> + <div class="form-group"> + <label for="apps_name"><?=$lang['admin']['apps_name'];?>:</label> + <input type="text" class="form-control" name="apps_name" placeholder="mailcow Apps" value="<?=$ui_texts['apps_name'];?>"> + </div> + <div class="form-group"> + <label for="help_text"><?=$lang['admin']['help_text'];?>:</label> + <textarea class="form-control" id="help_text" name="help_text" rows="7"><?=$ui_texts['help_text'];?></textarea> + </div> + <button class="btn btn-default" data-action="edit_selected" data-item="ui" data-id="uitexts" data-api-url='edit/ui_texts' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> + </form> + </div> </div> </div> </div> </div> </div> + <div role="tabpanel" class="tab-pane" id="tab-sys-mails"> + <div class="panel panel-default"> + <div class="panel-heading"><?=$lang['admin']['sys_mails'];?></div> + <div class="panel-body"> + <form class="form-horizontal" autocapitalize="none" data-id="admin" autocorrect="off" role="form" method="post"> + <div class="form-group"> + <label class="control-label col-sm-2" for="mass_from"><?=$lang['admin']['from'];?>:</label> + <div class="col-sm-10"> + <input type="email" class="form-control" name="mass_from" value="noreply@<?=getenv('MAILCOW_HOSTNAME');;?>" required> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="mass_subject"><?=$lang['admin']['subject'];?>:</label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="mass_subject" required> + </div> + </div> + <?php + $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); + if (!empty($domains)) { + foreach ($domains as $domain) { + foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) { + $mailboxes[] = $mailbox; + } + } + } + ?> + <div class="form-group"> + <label class="control-label col-sm-2" for="mass_subject"><?=$lang['admin']['include_exclude'];?>: + <p class="help-block"><?=$lang['admin']['include_exclude_info'];?></p> + </label> + <div class="col-sm-5"> + <label class="control-label" for="mass_exclude"><?=$lang['admin']['excludes'];?>:</label> + <select id="mass_exclude" name="mass_exclude[]" data-live-search="true" data-width="100%" size="30" multiple> + <?php + if (!empty($mailboxes)) { + foreach (array_filter($mailboxes) as $mailbox): + ?> + <option><?=htmlspecialchars($mailbox);?></option> + <?php + endforeach; + } + ?> + </select> + </div> + <div class="col-sm-5"> + <label class="control-label" for="mass_include"><?=$lang['admin']['includes'];?>:</label> + <select id="mass_include" name="mass_include[]" data-live-search="true" data-width="100%" size="30" multiple> + <?php + if (!empty($mailboxes)) { + foreach (array_filter($mailboxes) as $mailbox): + ?> + <option><?=htmlspecialchars($mailbox);?></option> + <?php + endforeach; + } + ?> + </select> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="mass_text"><?=$lang['admin']['text'];?>:</label> + <div class="col-sm-10"> + <textarea class="form-control" rows="10" name="mass_text" id="mass_text" required></textarea> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <label> + <input type="checkbox" id="mass_disarm"> <?=$lang['admin']['activate_send'];?> + </label> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-default" type="submit" id="mass_send" name="mass_send" disabled><span class="glyphicon glyphicon-envelope"></span> <?=$lang['admin']['send'];?></button> + </div> + </div> + </form> + </div> + </div> </div> + + <div role="tabpanel" class="tab-pane" id="tab-mailq"> + <div class="panel panel-default"> + <div class="panel-heading"> + <?=$lang['admin']['queue_manager'];?> <span class="badge badge-info table-lines"></span> + <div class="btn-group pull-right"> + <button class="btn btn-xs btn-default refresh_table" data-draw="draw_queue" data-table="queuetable"><?=$lang['admin']['refresh'];?></button> + </div> + </div> + <div class="panel-body"> + <div class="table-responsive"> + <table class="table table-striped table-condensed" id="queuetable"></table> + </div> + <div class="mass-actions-admin"> + <div class="btn-group"> + <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="mailqitems" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> + <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> + <ul class="dropdown-menu"> + <li><a data-toggle="tooltip" title="postqueue -i" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"deliver"}' href="#"><?=$lang['admin']['queue_deliver_mail'];?></a></li> + <li><a data-toggle="tooltip" title="postsuper -H" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"unhold"}' href="#"><?=$lang['admin']['queue_unhold_mail'];?></a></li> + <li><a data-toggle="tooltip" title="postsuper -h" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"hold"}' href="#"><?=$lang['admin']['queue_hold_mail'];?></a></li> + <li role="separator" class="divider"></li> + <li><a data-toggle="tooltip" title="postsuper -d" data-action="delete_selected" data-id="mailqitems" data-api-url='delete/mailq' href="#"><?=$lang['mailbox']['remove'];?></a></li> + </ul> + <a class="btn btn-sm btn-primary" + data-action="edit_selected" + data-item="mailqitems-all" + data-api-url='edit/mailq' + data-api-attr='{"action":"flush"}' + data-toggle="tooltip" title="postqueue -f" + href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['admin']['flush_queue'];?></a> + <a class="btn btn-sm btn-danger" + id="super_delete" + data-action="edit_selected" + data-item="mailqitems-all" + data-api-url='edit/mailq' + data-api-attr='{"action":"super_delete"}' + data-toggle="tooltip" title="postsuper -d ALL" + href="#"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> <?=$lang['admin']['delete_queue'];?></a> + </div> + </div> + </div> + </div> + </div> + + </div> <!-- /tab-content --> + </div> <!-- /col-md-12 --> + </div> <!-- /row --> </div> <!-- /container --> <?php require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/admin.php'; @@ -786,14 +1067,14 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/admin.php'; <?php $lang_admin = json_encode($lang['admin']); echo "var lang = ". $lang_admin . ";\n"; +echo "var admin_username = '". $_SESSION['mailcow_cc_username'] . "';\n"; echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n"; echo "var pagination_size = '". $PAGINATION_SIZE . "';\n"; echo "var log_pagination_size = '". $LOG_PAGINATION_SIZE . "';\n"; ?> </script> -<script src="js/footable.min.js"></script> -<script src="js/admin.js"></script> <?php +$js_minifier->add('/web/js/site/admin.js'); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; } else { header('Location: /'); diff --git a/data/web/autoconfig.php b/data/web/autoconfig.php index 58d6fec1..95952df0 100644 --- a/data/web/autoconfig.php +++ b/data/web/autoconfig.php @@ -1,5 +1,5 @@ <?php -require_once 'inc/vars.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; $default_autodiscover_config = $autodiscover_config; if(file_exists('inc/vars.local.inc.php')) { include_once 'inc/vars.local.inc.php'; diff --git a/data/web/autodiscover-json.php b/data/web/autodiscover-json.php index 56878838..c8bb678e 100644 --- a/data/web/autodiscover-json.php +++ b/data/web/autodiscover-json.php @@ -1,6 +1,6 @@ <?php -require_once 'inc/vars.inc.php'; -require_once 'inc/functions.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; $default_autodiscover_config = $autodiscover_config; if(file_exists('inc/vars.local.inc.php')) { include_once 'inc/vars.local.inc.php'; @@ -8,10 +8,10 @@ if(file_exists('inc/vars.local.inc.php')) { $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config); header('Content-type: application/json'); -if ($_GET['Protocol'] == 'ActiveSync') { +if (strtolower($_GET['Protocol']) == 'activesync') { echo '{"Protocol":"ActiveSync","Url":"' . $autodiscover_config['activesync']['url'] . '"}'; } -elseif ($_GET['Protocol'] == 'AutodiscoverV1') { +elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') { echo '{"Protocol":"AutodiscoverV1","Url":"https://' . $_SERVER['HTTP_HOST'] . '/Autodiscover/Autodiscover.xml"}'; } else { diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index f5bcbf8f..8f17b70b 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -1,6 +1,6 @@ <?php -require_once 'inc/vars.inc.php'; -require_once 'inc/functions.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; $default_autodiscover_config = $autodiscover_config; if(file_exists('inc/vars.local.inc.php')) { include_once 'inc/vars.local.inc.php'; @@ -27,7 +27,8 @@ if (strpos($data, 'autodiscover/outlook/responseschema') !== false) { } } -$dsn = $database_type . ":host=" . $database_host . ";dbname=" . $database_name; +//$dsn = $database_type . ":host=" . $database_host . ";dbname=" . $database_name; +$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, diff --git a/data/web/css/bootstrap-slider.min.css b/data/web/css/bootstrap-slider.min.css deleted file mode 100644 index e55300b2..00000000 --- a/data/web/css/bootstrap-slider.min.css +++ /dev/null @@ -1,41 +0,0 @@ -/*! ======================================================= - VERSION 9.7.2 -========================================================= */ -/*! ========================================================= - * bootstrap-slider.js - * - * Maintainers: - * Kyle Kemp - * - Twitter: @seiyria - * - Github: seiyria - * Rohit Kalkur - * - Twitter: @Rovolutionary - * - Github: rovolution - * - * ========================================================= - * - * bootstrap-slider is released under the MIT License - * Copyright (c) 2017 Kyle Kemp, Rohit Kalkur, and contributors - * - * 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. - * - * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#0480be;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#0480be;border-right-color:#0480be;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#149bdf 0,#0480be 100%);background-image:-o-linear-gradient(top,#149bdf 0,#0480be 100%);background-image:linear-gradient(to bottom,#149bdf 0,#0480be 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0);opacity:1} \ No newline at end of file diff --git a/data/web/css/bootstrap.min.css b/data/web/css/bootstrap.min.css deleted file mode 100644 index ac7801f8..00000000 --- a/data/web/css/bootstrap.min.css +++ /dev/null @@ -1,10 +0,0 @@ - * bootswatch v3.3.7 - * Homepage: http://bootswatch.com - * Copyright 2012-2017 Thomas Park - * Licensed under MIT - * Based on Bootstrap -*//*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#158cba;text-decoration:none}a:hover,a:focus{color:#158cba;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:5px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:400;line-height:1.1;color:#333333}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{background-color:#ff851b;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#158cba}a.text-primary:hover,a.text-primary:focus{color:#106a8c}.text-success{color:#ffffff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover,a.text-danger:focus{color:#e6e6e6}.bg-primary{color:#fff;background-color:#158cba}a.bg-primary:hover,a.bg-primary:focus{background-color:#106a8c}.bg-success{background-color:#28b62c}a.bg-success:hover,a.bg-success:focus{background-color:#1f8c22}.bg-info{background-color:#75caeb}a.bg-info:hover,a.bg-info:focus{background-color:#48b9e5}.bg-warning{background-color:#ff851b}a.bg-warning:hover,a.bg-warning:focus{background-color:#e76b00}.bg-danger{background-color:#ff4136}a.bg-danger:hover,a.bg-danger:focus{background-color:#ff1103}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:2px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333333;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1450px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1450px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #eeeeee}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #eeeeee}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #eeeeee}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#28b62c}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#23a127}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#75caeb}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#5fc1e8}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#ff851b}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#ff7701}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#ff4136}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ff291c}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #eeeeee}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:8px;font-size:14px;line-height:1.42857143;color:#555555}.form-control{display:block;width:100%;height:38px;padding:7px 12px;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff;background-image:none;border:1px solid #e7e7e7;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:38px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:28px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:52px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:8px;padding-bottom:8px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-sm{height:28px;line-height:28px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.form-group-sm select.form-control{height:28px;line-height:28px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:28px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-lg{height:52px;line-height:52px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.form-group-lg select.form-control{height:52px;line-height:52px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:52px;min-height:38px;padding:14px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:47.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:38px;height:38px;line-height:38px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:52px;height:52px;line-height:52px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:28px;height:28px;line-height:28px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#ffffff}.has-success .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#28b62c}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#ffffff}.has-warning .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#ff851b}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#ffffff}.has-error .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#ff4136}.has-error .form-control-feedback{color:#ffffff}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#959595}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:8px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:28px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:8px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:5px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:7px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#555555;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.btn-default:focus,.btn-default.focus{color:#555555;background-color:#d5d5d5;border-color:#a2a2a2}.btn-default:hover{color:#555555;background-color:#d5d5d5;border-color:#c3c3c3}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#555555;background-color:#d5d5d5;border-color:#c3c3c3}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#555555;background-color:#c3c3c3;border-color:#a2a2a2}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#eeeeee;border-color:#e2e2e2}.btn-default .badge{color:#eeeeee;background-color:#555555}.btn-primary{color:#ffffff;background-color:#158cba;border-color:#127ba3}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#106a8c;border-color:#052531}.btn-primary:hover{color:#ffffff;background-color:#106a8c;border-color:#0c516c}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#106a8c;border-color:#0c516c}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#0c516c;border-color:#052531}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#158cba;border-color:#127ba3}.btn-primary .badge{color:#158cba;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#28b62c;border-color:#23a127}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#1f8c22;border-color:#0c390e}.btn-success:hover{color:#ffffff;background-color:#1f8c22;border-color:#186f1b}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#1f8c22;border-color:#186f1b}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#186f1b;border-color:#0c390e}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#28b62c;border-color:#23a127}.btn-success .badge{color:#28b62c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#75caeb;border-color:#5fc1e8}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#48b9e5;border-color:#1984ae}.btn-info:hover{color:#ffffff;background-color:#48b9e5;border-color:#29ade0}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#48b9e5;border-color:#29ade0}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#29ade0;border-color:#1984ae}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#75caeb;border-color:#5fc1e8}.btn-info .badge{color:#75caeb;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#e76b00;border-color:#813c00}.btn-warning:hover{color:#ffffff;background-color:#e76b00;border-color:#c35b00}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#e76b00;border-color:#c35b00}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#c35b00;border-color:#813c00}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#ff851b;border-color:#ff7701}.btn-warning .badge{color:#ff851b;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#ff4136;border-color:#ff291c}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#ff1103;border-color:#9c0900}.btn-danger:hover{color:#ffffff;background-color:#ff1103;border-color:#de0c00}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#ff1103;border-color:#de0c00}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#de0c00;border-color:#9c0900}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#ff4136;border-color:#ff291c}.btn-danger .badge{color:#ff4136;background-color:#ffffff}.btn-link{color:#158cba;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#158cba;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.btn-sm,.btn-group-sm>.btn{padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:2px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:0.35s;-o-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#ffffff;border:1px solid #cccccc;border:1px solid #e7e7e7;border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);-webkit-background-clip:padding-box;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#eeeeee}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#333333;background-color:transparent}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#158cba}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#eeeeee}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:52px;line-height:52px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:28px;line-height:28px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:7px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555555;text-align:center;background-color:#eeeeee;border:1px solid #e7e7e7;border-radius:4px}.input-group-addon.input-sm{padding:4px 10px;font-size:12px;border-radius:2px}.input-group-addon.input-lg{padding:13px 16px;font-size:18px;border-radius:5px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#ffffff}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#ffffff;border-color:#158cba}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #e7e7e7}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #e7e7e7}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555555;background-color:#ffffff;border:1px solid #e7e7e7;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#158cba}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:6px;margin-bottom:6px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:6px;margin-bottom:6px}.navbar-btn.btn-sm{margin-top:11px;margin-bottom:11px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#333333}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-default .navbar-text{color:#555555}.navbar-default .navbar-nav>li>a{color:#999999}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-default .navbar-toggle{border-color:#eeeeee}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ffffff}.navbar-default .navbar-toggle .icon-bar{background-color:#999999}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:transparent;color:#333333}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-default .navbar-link{color:#999999}.navbar-default .navbar-link:hover{color:#333333}.navbar-default .btn-link{color:#999999}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#eeeeee}.navbar-inverse{background-color:#ffffff;border-color:#e6e6e6}.navbar-inverse .navbar-brand{color:#999999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-text{color:#999999}.navbar-inverse .navbar-nav>li>a{color:#999999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#eeeeee}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#eeeeee}.navbar-inverse .navbar-toggle .icon-bar{background-color:#999999}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#ededed}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:transparent;color:#333333}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-inverse .navbar-link{color:#999999}.navbar-inverse .navbar-link:hover{color:#333333}.navbar-inverse .btn-link{color:#999999}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#333333}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#eeeeee}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#fafafa;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:">\00a0";padding:0 5px;color:#999999}.breadcrumb>.active{color:#999999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:7px 12px;line-height:1.42857143;text-decoration:none;color:#555555;background-color:#eeeeee;border:1px solid #e2e2e2;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;background-color:#158cba;border-color:#127ba3;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;background-color:#eeeeee;border-color:#e2e2e2;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:13px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:5px;border-top-left-radius:5px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:5px;border-top-right-radius:5px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:2px;border-top-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:2px;border-top-right-radius:2px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#eeeeee;border:1px solid #e2e2e2;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;background-color:#eeeeee;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#158cba}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#106a8c}.label-success{background-color:#28b62c}.label-success[href]:hover,.label-success[href]:focus{background-color:#1f8c22}.label-info{background-color:#75caeb}.label-info[href]:hover,.label-info[href]:focus{background-color:#48b9e5}.label-warning{background-color:#ff851b}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#e76b00}.label-danger{background-color:#ff4136}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#ff1103}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:normal;color:#ffffff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#158cba;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#158cba;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{border-radius:5px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#158cba}.thumbnail .caption{padding:9px;color:#555555}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#28b62c;border-color:#24a528;color:#ffffff}.alert-success hr{border-top-color:#209023}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#75caeb;border-color:#40b5e3;color:#ffffff}.alert-info hr{border-top-color:#29ade0}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#ff851b;border-color:#ff7701;color:#ffffff}.alert-warning hr{border-top-color:#e76b00}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#ff4136;border-color:#ff1103;color:#ffffff}.alert-danger hr{border-top-color:#e90d00}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#fafafa;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#158cba;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#28b62c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#75caeb}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#ff851b}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#ff4136}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #eeeeee}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#555555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eeeeee;color:#999999;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#158cba;border-color:#158cba}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a6dff5}.list-group-item-success{color:#ffffff;background-color:#28b62c}a.list-group-item-success,button.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#ffffff;background-color:#23a127}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#75caeb}a.list-group-item-info,button.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#ffffff;background-color:#5fc1e8}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#ff851b}a.list-group-item-warning,button.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#ffffff;background-color:#ff7701}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#ff4136}a.list-group-item-danger,button.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#ffffff;background-color:#ff291c}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid transparent;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #eeeeee}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid transparent}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid transparent}.panel-default{border-color:transparent}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:transparent}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-primary{border-color:transparent}.panel-primary>.panel-heading{color:#ffffff;background-color:#158cba;border-color:transparent}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-primary>.panel-heading .badge{color:#158cba;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-success{border-color:transparent}.panel-success>.panel-heading{color:#ffffff;background-color:#28b62c;border-color:transparent}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-success>.panel-heading .badge{color:#28b62c;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-info{border-color:transparent}.panel-info>.panel-heading{color:#ffffff;background-color:#75caeb;border-color:transparent}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-info>.panel-heading .badge{color:#75caeb;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-warning{border-color:transparent}.panel-warning>.panel-heading{color:#ffffff;background-color:#ff851b;border-color:transparent}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-warning>.panel-heading .badge{color:#ff851b;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-danger{border-color:transparent}.panel-danger>.panel-heading{color:#ffffff;background-color:#ff4136;border-color:transparent}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-danger>.panel-heading .badge{color:#ff4136;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#fafafa;border:1px solid #e8e8e8;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:5px}.well-sm{padding:9px;border-radius:2px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#ffffff;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#ffffff;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;border:1px solid #eeeeee;border:1px solid rgba(0,0,0,0.05);border-radius:5px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);-webkit-background-clip:padding-box;background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:14px;background-color:#ffffff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:4px 4px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0.0001)));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.0001)), to(rgba(0,0,0,0.5)));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1449px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1449px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1449px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1449px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1450px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1450px){.visible-lg-block{display:block !important}}@media (min-width:1450px){.visible-lg-inline{display:inline !important}}@media (min-width:1450px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1449px){.hidden-md{display:none !important}}@media (min-width:1450px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{border-width:0 1px 4px 1px}.btn{padding:9px 12px 7px;border-width:0 1px 4px 1px;font-size:12px;font-weight:bold;line-height:1.5;text-transform:uppercase}.btn:hover{margin-top:1px;border-bottom-width:3px}.btn:active{margin-top:2px;border-bottom-width:2px;-webkit-box-shadow:none;box-shadow:none}.btn-lg,.btn-group-lg>.btn{padding:15px 16px 13px;line-height:15px}.btn-sm,.btn-group-sm>.btn{padding:6px 10px 4px}.btn-xs,.btn-group-xs>.btn{padding:3px 5px 1px}.btn-default:hover,.btn-default:focus,.btn-group.open .dropdown-toggle.btn-default{background-color:#eeeeee;border-color:#e2e2e2}.btn-primary:hover,.btn-primary:focus,.btn-group.open .dropdown-toggle.btn-primary{background-color:#158cba;border-color:#127ba3}.btn-success:hover,.btn-success:focus,.btn-group.open .dropdown-toggle.btn-success{background-color:#28b62c;border-color:#23a127}.btn-info:hover,.btn-info:focus,.btn-group.open .dropdown-toggle.btn-info{background-color:#75caeb;border-color:#5fc1e8}.btn-warning:hover,.btn-warning:focus,.btn-group.open .dropdown-toggle.btn-warning{background-color:#ff851b;border-color:#ff7701}.btn-danger:hover,.btn-danger:focus,.btn-group.open .dropdown-toggle.btn-danger{background-color:#ff4136;border-color:#ff291c}.btn-group.open .dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.navbar-btn:hover{margin-top:8px}.navbar-btn:active{margin-top:9px}.navbar-btn.btn-sm:hover{margin-top:11px}.navbar-btn.btn-sm:active{margin-top:12px}.navbar-btn.btn-xs:hover{margin-top:15px}.navbar-btn.btn-xs:active{margin-top:16px}.btn-group-vertical .btn+.btn:hover{border-top-width:1px}.btn-group-vertical .btn+.btn:active{border-top-width:2px}.text-primary,.text-primary:hover{color:#158cba}.text-success,.text-success:hover{color:#28b62c}.text-danger,.text-danger:hover{color:#ff4136}.text-warning,.text-warning:hover{color:#ff851b}.text-info,.text-info:hover{color:#75caeb}table a:not(.btn),.table a:not(.btn){text-decoration:underline}table .dropdown-menu a,.table .dropdown-menu a{text-decoration:none}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{color:#fff}table .success a:not(.btn),.table .success a:not(.btn),table .warning a:not(.btn),.table .warning a:not(.btn),table .danger a:not(.btn),.table .danger a:not(.btn),table .info a:not(.btn),.table .info a:not(.btn){color:#fff}table:not(.table-bordered)>thead>tr>th,.table:not(.table-bordered)>thead>tr>th,table:not(.table-bordered)>tbody>tr>th,.table:not(.table-bordered)>tbody>tr>th,table:not(.table-bordered)>tfoot>tr>th,.table:not(.table-bordered)>tfoot>tr>th,table:not(.table-bordered)>thead>tr>td,.table:not(.table-bordered)>thead>tr>td,table:not(.table-bordered)>tbody>tr>td,.table:not(.table-bordered)>tbody>tr>td,table:not(.table-bordered)>tfoot>tr>td,.table:not(.table-bordered)>tfoot>tr>td{border-color:transparent}.form-control{-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.075);box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}label{font-weight:normal}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#ff851b}.has-warning .form-control,.has-warning .form-control:focus{border:1px solid #ff851b;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.075);box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-warning .input-group-addon{border:1px solid #ff851b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#ff4136}.has-error .form-control,.has-error .form-control:focus{border:1px solid #ff4136;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.075);box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-error .input-group-addon{border:1px solid #ff4136}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#28b62c}.has-success .form-control,.has-success .form-control:focus{border:1px solid #28b62c;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.075);box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-success .input-group-addon{border:1px solid #28b62c}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.nav-tabs>li>a{margin-top:6px;border-color:#e7e7e7;color:#333333;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus,.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus,.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{padding-bottom:16px;margin-top:0}.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{border-color:#e7e7e7}.nav-tabs>li.disabled>a:hover,.nav-tabs>li.disabled>a:focus{padding-top:10px;padding-bottom:10px;margin-top:6px}.nav-tabs.nav-justified>li{vertical-align:bottom}.dropdown-menu{margin-top:0;border-width:0 1px 4px 1px;border-top-width:1px;-webkit-box-shadow:none;box-shadow:none}.breadcrumb{border-color:#ededed;border-style:solid;border-width:0 1px 4px 1px}.pagination>li>a,.pager>li>a,.pagination>li>span,.pager>li>span{position:relative;top:0;border-width:0 1px 4px 1px;color:#555555;font-size:12px;font-weight:bold;text-transform:uppercase}.pagination>li>a:hover,.pager>li>a:hover,.pagination>li>span:hover,.pager>li>span:hover{top:1px;border-bottom-width:3px}.pagination>li>a:active,.pager>li>a:active,.pagination>li>span:active,.pager>li>span:active{top:2px;border-bottom-width:2px}.pagination>.disabled>a:hover,.pager>.disabled>a:hover,.pagination>.disabled>span:hover,.pager>.disabled>span:hover{top:0;border-width:0 1px 4px 1px}.pagination>.disabled>a:active,.pager>.disabled>a:active,.pagination>.disabled>span:active,.pager>.disabled>span:active{top:0;border-width:0 1px 4px 1px}.pager>li>a,.pager>li>span,.pager>.disabled>a,.pager>.disabled>span,.pager>li>a:hover,.pager>li>span:hover,.pager>.disabled>a:hover,.pager>.disabled>span:hover,.pager>li>a:active,.pager>li>span:active,.pager>.disabled>a:active,.pager>.disabled>span:active{border-left-width:2px;border-right-width:2px}.close{color:#fff;text-decoration:none;opacity:0.4}.close:hover,.close:focus{color:#fff;opacity:1}.alert{border-width:0 1px 4px 1px}.alert .alert-link{font-weight:normal;color:#fff;text-decoration:underline}.label{font-weight:normal}.progress{border:1px solid #e7e7e7;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.1);box-shadow:inset 0 2px 0 rgba(0,0,0,0.1)}.progress-bar{-webkit-box-shadow:inset 0 -4px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -4px 0 rgba(0,0,0,0.15)}.well{border:1px solid #e7e7e7;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.05);box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border-color:#eeeeee}a.list-group-item-success.active{background-color:#28b62c}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#23a127}a.list-group-item-warning.active{background-color:#ff851b}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#ff7701}a.list-group-item-danger.active{background-color:#ff4136}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ff291c}.jumbotron{border:1px solid #e7e7e7;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.05);box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}.panel{border:1px solid #e7e7e7;border-width:0 1px 4px 1px}.panel-default .close{color:#555555}.modal .close{color:#555555}.popover{color:#555555} \ No newline at end of file diff --git a/data/web/css/build/001-bootstrap.min.css b/data/web/css/build/001-bootstrap.min.css new file mode 100644 index 00000000..769bf30c --- /dev/null +++ b/data/web/css/build/001-bootstrap.min.css @@ -0,0 +1,11 @@ +/*! + * bootswatch v3.4.1 + * Homepage: http://bootswatch.com + * Copyright 2012-2019 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*//*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{color:#000 !important;text-shadow:none !important;background:transparent !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:"Glyphicons Halflings";src:url("/fonts/glyphicons-halflings-regular.eot");src:url("/fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("/fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("/fonts/glyphicons-halflings-regular.woff") format("woff"),url("/fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:"Glyphicons Halflings";font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{box-sizing:border-box}*:before,*:after{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#158cba;text-decoration:none}a:hover,a:focus{color:#158cba;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:5px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:400;line-height:1.1;color:#333333}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{padding:.2em;background-color:#ff851b}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#158cba}a.text-primary:hover,a.text-primary:focus{color:#106a8c}.text-success{color:#ffffff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover,a.text-danger:focus{color:#e6e6e6}.bg-primary{color:#fff;background-color:#158cba}a.bg-primary:hover,a.bg-primary:focus{background-color:#106a8c}.bg-success{background-color:#28b62c}a.bg-success:hover,a.bg-success:focus{background-color:#1f8c22}.bg-info{background-color:#75caeb}a.bg-info:hover,a.bg-info:focus{background-color:#48b9e5}.bg-warning{background-color:#ff851b}a.bg-warning:hover,a.bg-warning:focus{background-color:#e76b00}.bg-danger{background-color:#ff4136}a.bg-danger:hover,a.bg-danger:focus{background-color:#ff1103}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eeeeee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:"\00A0 \2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:2px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*="col-"]{padding-right:0;padding-left:0}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}table col[class*="col-"]{position:static;display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #eeeeee}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #eeeeee}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #eeeeee}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#28b62c}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#23a127}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#75caeb}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#5fc1e8}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#ff851b}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#ff7701}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#ff4136}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ff291c}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #eeeeee}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type="search"]{box-sizing:border-box;-webkit-appearance:none;appearance:none}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:8px;font-size:14px;line-height:1.42857143;color:#555555}.form-control{display:block;width:100%;height:38px;padding:7px 12px;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff;background-image:none;border:1px solid #e7e7e7;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:38px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:28px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:52px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:8px;padding-bottom:8px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-sm{height:28px;line-height:28px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.form-group-sm select.form-control{height:28px;line-height:28px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:28px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-lg{height:52px;line-height:52px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.form-group-lg select.form-control{height:52px;line-height:52px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:52px;min-height:38px;padding:14px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:47.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:38px;height:38px;line-height:38px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:52px;height:52px;line-height:52px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:28px;height:28px;line-height:28px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#ffffff}.has-success .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;background-color:#28b62c;border-color:#ffffff}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#ffffff}.has-warning .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;background-color:#ff851b;border-color:#ffffff}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#ffffff}.has-error .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;background-color:#ff4136;border-color:#ffffff}.has-error .form-control-feedback{color:#ffffff}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#959595}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:8px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:28px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:8px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:5px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:7px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#555555;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:0.65;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.btn-default:focus,.btn-default.focus{color:#555555;background-color:#d5d5d5;border-color:#a2a2a2}.btn-default:hover{color:#555555;background-color:#d5d5d5;border-color:#c3c3c3}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#555555;background-color:#d5d5d5;background-image:none;border-color:#c3c3c3}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#555555;background-color:#c3c3c3;border-color:#a2a2a2}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#eeeeee;border-color:#e2e2e2}.btn-default .badge{color:#eeeeee;background-color:#555555}.btn-primary{color:#ffffff;background-color:#158cba;border-color:#127ba3}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#106a8c;border-color:#052531}.btn-primary:hover{color:#ffffff;background-color:#106a8c;border-color:#0c516c}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#106a8c;background-image:none;border-color:#0c516c}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#0c516c;border-color:#052531}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#158cba;border-color:#127ba3}.btn-primary .badge{color:#158cba;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#28b62c;border-color:#23a127}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#1f8c22;border-color:#0c390e}.btn-success:hover{color:#ffffff;background-color:#1f8c22;border-color:#186f1b}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#1f8c22;background-image:none;border-color:#186f1b}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#186f1b;border-color:#0c390e}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#28b62c;border-color:#23a127}.btn-success .badge{color:#28b62c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#75caeb;border-color:#5fc1e8}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#48b9e5;border-color:#1984ae}.btn-info:hover{color:#ffffff;background-color:#48b9e5;border-color:#29ade0}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#48b9e5;background-image:none;border-color:#29ade0}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#29ade0;border-color:#1984ae}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#75caeb;border-color:#5fc1e8}.btn-info .badge{color:#75caeb;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#e76b00;border-color:#813c00}.btn-warning:hover{color:#ffffff;background-color:#e76b00;border-color:#c35b00}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#e76b00;background-image:none;border-color:#c35b00}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#c35b00;border-color:#813c00}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#ff851b;border-color:#ff7701}.btn-warning .badge{color:#ff851b;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#ff4136;border-color:#ff291c}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#ff1103;border-color:#9c0900}.btn-danger:hover{color:#ffffff;background-color:#ff1103;border-color:#de0c00}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#ff1103;background-image:none;border-color:#de0c00}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#de0c00;border-color:#9c0900}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#ff4136;border-color:#ff291c}.btn-danger .badge{color:#ff4136;background-color:#ffffff}.btn-link{font-weight:400;color:#158cba;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#158cba;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.btn-sm,.btn-group-sm>.btn{padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:2px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition-property:height, visibility;transition-duration:0.35s;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid #e7e7e7;border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#eeeeee}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#333333;text-decoration:none;background-color:transparent}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;background-color:#158cba;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#eeeeee}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:52px;line-height:52px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:28px;line-height:28px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:7px 12px;font-size:14px;font-weight:400;line-height:1;color:#555555;text-align:center;background-color:#eeeeee;border:1px solid #e7e7e7;border-radius:4px}.input-group-addon.input-sm{padding:4px 10px;font-size:12px;border-radius:2px}.input-group-addon.input-lg{padding:13px 16px;font-size:18px;border-radius:5px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#ffffff}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#ffffff;border-color:#158cba}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #e7e7e7}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #e7e7e7}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555555;cursor:default;background-color:#ffffff;border:1px solid #e7e7e7;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#158cba}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:6px;margin-bottom:6px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:6px;margin-bottom:6px}.navbar-btn.btn-sm{margin-top:11px;margin-bottom:11px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#333333}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-default .navbar-text{color:#555555}.navbar-default .navbar-nav>li>a{color:#999999}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#333333;background-color:transparent}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#eeeeee}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ffffff}.navbar-default .navbar-toggle .icon-bar{background-color:#999999}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#999999}.navbar-default .navbar-link:hover{color:#333333}.navbar-default .btn-link{color:#999999}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#eeeeee}.navbar-inverse{background-color:#ffffff;border-color:#e6e6e6}.navbar-inverse .navbar-brand{color:#999999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-text{color:#999999}.navbar-inverse .navbar-nav>li>a{color:#999999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#333333;background-color:transparent}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#eeeeee}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#eeeeee}.navbar-inverse .navbar-toggle .icon-bar{background-color:#999999}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#ededed}.navbar-inverse .navbar-link{color:#999999}.navbar-inverse .navbar-link:hover{color:#333333}.navbar-inverse .btn-link{color:#999999}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#333333}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#eeeeee}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#fafafa;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#999999;content:">\00a0"}.breadcrumb>.active{color:#999999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:7px 12px;margin-left:-1px;line-height:1.42857143;color:#555555;text-decoration:none;background-color:#eeeeee;border:1px solid #e2e2e2}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;cursor:default;background-color:#158cba;border-color:#127ba3}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;cursor:not-allowed;background-color:#eeeeee;border-color:#e2e2e2}.pagination-lg>li>a,.pagination-lg>li>span{padding:13px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:5px;border-bottom-left-radius:5px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:5px;border-bottom-right-radius:5px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#eeeeee;border:1px solid #e2e2e2;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;cursor:not-allowed;background-color:#eeeeee}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#158cba}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#106a8c}.label-success{background-color:#28b62c}.label-success[href]:hover,.label-success[href]:focus{background-color:#1f8c22}.label-info{background-color:#75caeb}.label-info[href]:hover,.label-info[href]:focus{background-color:#48b9e5}.label-warning{background-color:#ff851b}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#e76b00}.label-danger{background-color:#ff4136}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#ff1103}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:normal;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#158cba;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#158cba;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:5px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#158cba}.thumbnail .caption{padding:9px;color:#555555}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#ffffff;background-color:#28b62c;border-color:#24a528}.alert-success hr{border-top-color:#209023}.alert-success .alert-link{color:#e6e6e6}.alert-info{color:#ffffff;background-color:#75caeb;border-color:#40b5e3}.alert-info hr{border-top-color:#29ade0}.alert-info .alert-link{color:#e6e6e6}.alert-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.alert-warning hr{border-top-color:#e76b00}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{color:#ffffff;background-color:#ff4136;border-color:#ff1103}.alert-danger hr{border-top-color:#e90d00}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#fafafa;border-radius:4px;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#158cba;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#28b62c}.progress-striped .progress-bar-success{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#75caeb}.progress-striped .progress-bar-info{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#ff851b}.progress-striped .progress-bar-warning{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#ff4136}.progress-striped .progress-bar-danger{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #eeeeee}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#999999;cursor:not-allowed;background-color:#eeeeee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#158cba;border-color:#158cba}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a6dff5}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{color:#555555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#ffffff;background-color:#28b62c}a.list-group-item-success,button.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#ffffff;background-color:#23a127}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#75caeb}a.list-group-item-info,button.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#ffffff;background-color:#5fc1e8}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#ff851b}a.list-group-item-warning,button.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#ffffff;background-color:#ff7701}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#ff4136}a.list-group-item-danger,button.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#ffffff;background-color:#ff291c}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid transparent;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #eeeeee}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid transparent}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid transparent}.panel-default{border-color:transparent}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:transparent}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-primary{border-color:transparent}.panel-primary>.panel-heading{color:#ffffff;background-color:#158cba;border-color:transparent}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-primary>.panel-heading .badge{color:#158cba;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-success{border-color:transparent}.panel-success>.panel-heading{color:#ffffff;background-color:#28b62c;border-color:transparent}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-success>.panel-heading .badge{color:#28b62c;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-info{border-color:transparent}.panel-info>.panel-heading{color:#ffffff;background-color:#75caeb;border-color:transparent}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-info>.panel-heading .badge{color:#75caeb;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-warning{border-color:transparent}.panel-warning>.panel-heading{color:#ffffff;background-color:#ff851b;border-color:transparent}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-warning>.panel-heading .badge{color:#ff851b;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-danger{border-color:transparent}.panel-danger>.panel-heading{color:#ffffff;background-color:#ff4136;border-color:transparent}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-danger>.panel-heading .badge{color:#ff4136;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#fafafa;border:1px solid #e8e8e8;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:5px}.well-sm{padding:9px;border-radius:2px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#ffffff;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);opacity:0.2}.close:hover,.close:focus{color:#ffffff;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:0.5}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);transform:translate(0, -25%);transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;background-clip:padding-box;border:1px solid #eeeeee;border:1px solid rgba(0,0,0,0.05);border-radius:5px;box-shadow:0 3px 9px rgba(0,0,0,0.5);outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:0.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:0.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:5px;box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#ffffff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#ffffff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#ffffff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:4px 4px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:0.5}.carousel-control.left{background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#ffffff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:0.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #ffffff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#ffffff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{border-width:0 1px 4px 1px}.btn{padding:9px 12px 7px;border-width:0 1px 4px 1px;font-size:12px;font-weight:bold;text-transform:uppercase}.btn:hover{margin-top:1px;border-bottom-width:3px}.btn:active{margin-top:2px;border-bottom-width:2px;box-shadow:none}.btn-lg,.btn-group-lg>.btn{padding:15px 16px 13px;line-height:15px}.btn-sm,.btn-group-sm>.btn{padding:6px 10px 4px}.btn-xs,.btn-group-xs>.btn{padding:3px 5px 1px}.btn-default:hover,.btn-default:focus,.btn-group.open .dropdown-toggle.btn-default{background-color:#eeeeee;border-color:#e2e2e2}.btn-primary:hover,.btn-primary:focus,.btn-group.open .dropdown-toggle.btn-primary{background-color:#158cba;border-color:#127ba3}.btn-success:hover,.btn-success:focus,.btn-group.open .dropdown-toggle.btn-success{background-color:#28b62c;border-color:#23a127}.btn-info:hover,.btn-info:focus,.btn-group.open .dropdown-toggle.btn-info{background-color:#75caeb;border-color:#5fc1e8}.btn-warning:hover,.btn-warning:focus,.btn-group.open .dropdown-toggle.btn-warning{background-color:#ff851b;border-color:#ff7701}.btn-danger:hover,.btn-danger:focus,.btn-group.open .dropdown-toggle.btn-danger{background-color:#ff4136;border-color:#ff291c}.btn-group.open .dropdown-toggle{box-shadow:none}.navbar-btn:hover{margin-top:8px}.navbar-btn:active{margin-top:9px}.navbar-btn.btn-sm:hover{margin-top:11px}.navbar-btn.btn-sm:active{margin-top:12px}.navbar-btn.btn-xs:hover{margin-top:15px}.navbar-btn.btn-xs:active{margin-top:16px}.btn-group-vertical .btn+.btn:hover{border-top-width:1px}.btn-group-vertical .btn+.btn:active{border-top-width:2px}.text-primary,.text-primary:hover{color:#158cba}.text-success,.text-success:hover{color:#28b62c}.text-danger,.text-danger:hover{color:#ff4136}.text-warning,.text-warning:hover{color:#ff851b}.text-info,.text-info:hover{color:#75caeb}table a:not(.btn),.table a:not(.btn){text-decoration:underline}table .dropdown-menu a,.table .dropdown-menu a{text-decoration:none}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{color:#fff}table .success a:not(.btn),.table .success a:not(.btn),table .warning a:not(.btn),.table .warning a:not(.btn),table .danger a:not(.btn),.table .danger a:not(.btn),table .info a:not(.btn),.table .info a:not(.btn){color:#fff}table:not(.table-bordered)>thead>tr>th,.table:not(.table-bordered)>thead>tr>th,table:not(.table-bordered)>tbody>tr>th,.table:not(.table-bordered)>tbody>tr>th,table:not(.table-bordered)>tfoot>tr>th,.table:not(.table-bordered)>tfoot>tr>th,table:not(.table-bordered)>thead>tr>td,.table:not(.table-bordered)>thead>tr>td,table:not(.table-bordered)>tbody>tr>td,.table:not(.table-bordered)>tbody>tr>td,table:not(.table-bordered)>tfoot>tr>td,.table:not(.table-bordered)>tfoot>tr>td{border-color:transparent}.form-control{box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}label{font-weight:normal}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#ff851b}.has-warning .form-control,.has-warning .form-control:focus{border:1px solid #ff851b;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-warning .input-group-addon{border:1px solid #ff851b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#ff4136}.has-error .form-control,.has-error .form-control:focus{border:1px solid #ff4136;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-error .input-group-addon{border:1px solid #ff4136}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#28b62c}.has-success .form-control,.has-success .form-control:focus{border:1px solid #28b62c;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-success .input-group-addon{border:1px solid #28b62c}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.nav-tabs>li>a{margin-top:6px;border-color:#e7e7e7;color:#333333;transition:all .2s ease-in-out}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus,.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus,.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{padding-bottom:16px;margin-top:0}.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{border-color:#e7e7e7}.nav-tabs>li.disabled>a:hover,.nav-tabs>li.disabled>a:focus{padding-top:10px;padding-bottom:10px;margin-top:6px}.nav-tabs.nav-justified>li{vertical-align:bottom}.dropdown-menu{margin-top:0;border-width:0 1px 4px 1px;border-top-width:1px;box-shadow:none}.breadcrumb{border-color:#ededed;border-style:solid;border-width:0 1px 4px 1px}.pagination>li>a,.pager>li>a,.pagination>li>span,.pager>li>span{position:relative;top:0;border-width:0 1px 4px 1px;color:#555555;font-size:12px;font-weight:bold;text-transform:uppercase}.pagination>li>a:hover,.pager>li>a:hover,.pagination>li>span:hover,.pager>li>span:hover{top:1px;border-bottom-width:3px}.pagination>li>a:active,.pager>li>a:active,.pagination>li>span:active,.pager>li>span:active{top:2px;border-bottom-width:2px}.pagination>.disabled>a:hover,.pager>.disabled>a:hover,.pagination>.disabled>span:hover,.pager>.disabled>span:hover{top:0;border-width:0 1px 4px 1px}.pagination>.disabled>a:active,.pager>.disabled>a:active,.pagination>.disabled>span:active,.pager>.disabled>span:active{top:0;border-width:0 1px 4px 1px}.pager>li>a,.pager>li>span,.pager>.disabled>a,.pager>.disabled>span,.pager>li>a:hover,.pager>li>span:hover,.pager>.disabled>a:hover,.pager>.disabled>span:hover,.pager>li>a:active,.pager>li>span:active,.pager>.disabled>a:active,.pager>.disabled>span:active{border-left-width:2px;border-right-width:2px}.close{color:#fff;text-decoration:none;opacity:0.4}.close:hover,.close:focus{color:#fff;opacity:1}.alert{border-width:0 1px 4px 1px}.alert .alert-link{font-weight:normal;color:#fff;text-decoration:underline}.label{font-weight:normal}.progress{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.1)}.progress-bar{box-shadow:inset 0 -4px 0 rgba(0,0,0,0.15)}.well{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border-color:#eeeeee}a.list-group-item-success.active{background-color:#28b62c}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#23a127}a.list-group-item-warning.active{background-color:#ff851b}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#ff7701}a.list-group-item-danger.active{background-color:#ff4136}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ff291c}.jumbotron{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}.panel{border:1px solid #e7e7e7;border-width:0 1px 4px 1px}.panel-default .close{color:#555555}.modal .close{color:#555555}.popover{color:#555555} \ No newline at end of file diff --git a/data/web/css/breakpoint.min.css b/data/web/css/build/002-breakpoint.min.css similarity index 100% rename from data/web/css/breakpoint.min.css rename to data/web/css/build/002-breakpoint.min.css diff --git a/data/web/css/bootstrap-select.min.css b/data/web/css/build/003-bootstrap-select.min.css similarity index 100% rename from data/web/css/bootstrap-select.min.css rename to data/web/css/build/003-bootstrap-select.min.css diff --git a/data/web/css/build/004-bootstrap-slider.min.css b/data/web/css/build/004-bootstrap-slider.min.css new file mode 100644 index 00000000..f8e395be --- /dev/null +++ b/data/web/css/build/004-bootstrap-slider.min.css @@ -0,0 +1,41 @@ +/*! ======================================================= + VERSION 10.6.1 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * bootstrap-slider is released under the MIT License + * Copyright (c) 2019 Kyle Kemp, Rohit Kalkur, and contributors + * + * 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. + * + * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;-ms-transform:translateY(-50%);transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#2e6da4;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal .tooltip{-ms-transform:translateX(-50%);transform:translateX(-50%)}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-horizontal.slider-rtl .tooltip{-ms-transform:translateX(50%);transform:translateX(50%)}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#2e6da4;border-right-color:#2e6da4;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical .tooltip{-ms-transform:translateY(-50%);transform:translateY(-50%)}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip{pointer-events:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7',endColorstr='#ff2e6da4',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle:hover{cursor:pointer}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;cursor:pointer;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0);opacity:1} \ No newline at end of file diff --git a/data/web/css/bootstrap-switch.min.css b/data/web/css/build/005-bootstrap-switch.min.css similarity index 100% rename from data/web/css/bootstrap-switch.min.css rename to data/web/css/build/005-bootstrap-switch.min.css diff --git a/data/web/css/footable.bootstrap.min.css b/data/web/css/build/006-footable.bootstrap.min.css similarity index 100% rename from data/web/css/footable.bootstrap.min.css rename to data/web/css/build/006-footable.bootstrap.min.css diff --git a/data/web/css/build/007-languages.min.css b/data/web/css/build/007-languages.min.css new file mode 100644 index 00000000..2c6a9262 --- /dev/null +++ b/data/web/css/build/007-languages.min.css @@ -0,0 +1 @@ +.lang-lg,.lang-sm,.lang-xs{background-repeat:no-repeat;display:inline-block;background-image:url(/img/languages.png)}.lang-sm,.lang-sm:after,.lang-xs,.lang-xs:after{position:relative}.lang-xs{background-position:0 -484px;min-width:14px;height:11px;min-height:11px;max-height:11px}.lang-sm{background-position:0 -1199px;min-width:22px;height:16px;min-height:16px;max-height:16px}.lang-lg{background-position:0 -2134px;min-width:30px;height:22px;min-height:22px;max-height:22px}.lang-xs[lang=ar]{background-position:0 0}.lang-xs[lang=be]{background-position:0 -11px}.lang-xs[lang=bg]{background-position:0 -22px}.lang-xs[lang=cs]{background-position:0 -33px}.lang-xs[lang=da]{background-position:0 -44px}.lang-xs[lang=de]{background-position:0 -55px}.lang-xs[lang=el]{background-position:0 -66px}.lang-xs[lang=en]{background-position:0 -77px}.lang-xs[lang=es]{background-position:0 -88px}.lang-xs[lang=et]{background-position:0 -99px}.lang-xs[lang=fi]{background-position:0 -110px}.lang-xs[lang=fr]{background-position:0 -121px}.lang-xs[lang=ga]{background-position:0 -132px}.lang-xs[lang=hi]{background-position:0 -143px}.lang-xs[lang=hr]{background-position:0 -154px}.lang-xs[lang=hu]{background-position:0 -165px}.lang-xs[lang=in]{background-position:0 -176px}.lang-xs[lang=is]{background-position:0 -187px}.lang-xs[lang=it]{background-position:0 -198px}.lang-xs[lang=iw]{background-position:0 -209px}.lang-xs[lang=ja]{background-position:0 -220px}.lang-xs[lang=ko]{background-position:0 -231px}.lang-xs[lang=lt]{background-position:0 -242px}.lang-xs[lang=lv]{background-position:0 -253px}.lang-xs[lang=mk]{background-position:0 -264px}.lang-xs[lang=ms]{background-position:0 -275px}.lang-xs[lang=mt]{background-position:0 -286px}.lang-xs[lang=nl]{background-position:0 -297px}.lang-xs[lang=no]{background-position:0 -308px}.lang-xs[lang=pl]{background-position:0 -319px}.lang-xs[lang=pt]{background-position:0 -330px}.lang-xs[lang=ro]{background-position:0 -341px}.lang-xs[lang=ru]{background-position:0 -352px}.lang-xs[lang=sk]{background-position:0 -363px}.lang-xs[lang=sl]{background-position:0 -374px}.lang-xs[lang=sq]{background-position:0 -385px}.lang-xs[lang=sr]{background-position:0 -396px}.lang-xs[lang=sv]{background-position:0 -407px}.lang-xs[lang=th]{background-position:0 -418px}.lang-xs[lang=tr]{background-position:0 -429px}.lang-xs[lang=uk]{background-position:0 -440px}.lang-xs[lang=vi]{background-position:0 -451px}.lang-xs[lang=zh]{background-position:0 -462px}.lang-xs[lang=ca]{background-position:0 -473px}.lang-sm[lang=ar]{background-position:0 -495px}.lang-sm[lang=be]{background-position:0 -511px}.lang-sm[lang=bg]{background-position:0 -527px}.lang-sm[lang=cs]{background-position:0 -543px}.lang-sm[lang=da]{background-position:0 -559px}.lang-sm[lang=de]{background-position:0 -575px}.lang-sm[lang=el]{background-position:0 -591px}.lang-sm[lang=en]{background-position:0 -607px}.lang-sm[lang=es]{background-position:0 -623px}.lang-sm[lang=et]{background-position:0 -639px}.lang-sm[lang=fi]{background-position:0 -655px}.lang-sm[lang=fr]{background-position:0 -671px}.lang-sm[lang=ga]{background-position:0 -687px}.lang-sm[lang=hi]{background-position:0 -703px}.lang-sm[lang=hr]{background-position:0 -719px}.lang-sm[lang=hu]{background-position:0 -735px}.lang-sm[lang=in]{background-position:0 -751px}.lang-sm[lang=is]{background-position:0 -767px}.lang-sm[lang=it]{background-position:0 -783px}.lang-sm[lang=iw]{background-position:0 -799px}.lang-sm[lang=ja]{background-position:0 -815px}.lang-sm[lang=ko]{background-position:0 -831px}.lang-sm[lang=lt]{background-position:0 -847px}.lang-sm[lang=lv]{background-position:0 -863px}.lang-sm[lang=mk]{background-position:0 -879px}.lang-sm[lang=ms]{background-position:0 -895px}.lang-sm[lang=mt]{background-position:0 -911px}.lang-sm[lang=nl]{background-position:0 -927px}.lang-sm[lang=no]{background-position:0 -943px}.lang-sm[lang=pl]{background-position:0 -959px}.lang-sm[lang=pt]{background-position:0 -975px}.lang-sm[lang=ro]{background-position:0 -991px}.lang-sm[lang=ru]{background-position:0 -1007px}.lang-sm[lang=sk]{background-position:0 -1023px}.lang-sm[lang=sl]{background-position:0 -1039px}.lang-sm[lang=sq]{background-position:0 -1055px}.lang-sm[lang=sr]{background-position:0 -1071px}.lang-sm[lang=sv]{background-position:0 -1087px}.lang-sm[lang=th]{background-position:0 -1103px}.lang-sm[lang=tr]{background-position:0 -1119px}.lang-sm[lang=uk]{background-position:0 -1135px}.lang-sm[lang=vi]{background-position:0 -1151px}.lang-sm[lang=zh]{background-position:0 -1167px}.lang-sm[lang=ca]{background-position:0 -1183px}.lang-lg[lang=ar]{background-position:0 -1188px}.lang-lg[lang=be]{background-position:0 -1210px}.lang-lg[lang=bg]{background-position:0 -1232px}.lang-lg[lang=cs]{background-position:0 -1254px}.lang-lg[lang=da]{background-position:0 -1276px}.lang-lg[lang=de]{background-position:0 -1298px}.lang-lg[lang=el]{background-position:0 -1320px}.lang-lg[lang=en]{background-position:0 -1342px}.lang-lg[lang=es]{background-position:0 -1364px}.lang-lg[lang=et]{background-position:0 -1386px}.lang-lg[lang=fi]{background-position:0 -1408px}.lang-lg[lang=fr]{background-position:0 -1430px}.lang-lg[lang=ga]{background-position:0 -1452px}.lang-lg[lang=hi]{background-position:0 -1474px}.lang-lg[lang=hr]{background-position:0 -1496px}.lang-lg[lang=hu]{background-position:0 -1518px}.lang-lg[lang=in]{background-position:0 -1540px}.lang-lg[lang=is]{background-position:0 -1562px}.lang-lg[lang=it]{background-position:0 -1584px}.lang-lg[lang=iw]{background-position:0 -1606px}.lang-lg[lang=ja]{background-position:0 -1628px}.lang-lg[lang=ko]{background-position:0 -1650px}.lang-lg[lang=lt]{background-position:0 -1672px}.lang-lg[lang=lv]{background-position:0 -1694px}.lang-lg[lang=mk]{background-position:0 -1716px}.lang-lg[lang=ms]{background-position:0 -1738px}.lang-lg[lang=mt]{background-position:0 -1760px}.lang-lg[lang=nl]{background-position:0 -1782px}.lang-lg[lang=no]{background-position:0 -1804px}.lang-lg[lang=pl]{background-position:0 -1826px}.lang-lg[lang=pt]{background-position:0 -1848px}.lang-lg[lang=ro]{background-position:0 -1870px}.lang-lg[lang=ru]{background-position:0 -1892px}.lang-lg[lang=sk]{background-position:0 -1914px}.lang-lg[lang=sl]{background-position:0 -1936px}.lang-lg[lang=sq]{background-position:0 -1958px}.lang-lg[lang=sr]{background-position:0 -1980px}.lang-lg[lang=sv]{background-position:0 -2002px}.lang-lg[lang=th]{background-position:0 -2024px}.lang-lg[lang=tr]{background-position:0 -2046px}.lang-lg[lang=uk]{background-position:0 -2068px}.lang-lg[lang=vi]{background-position:0 -2090px}.lang-lg[lang=zh]{background-position:0 -2112px}.lang-lbl-en:after,.lang-lbl-full:after,.lang-lbl:after{content:"Unknown language"}.lang-lbl[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629"}.lang-lbl[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456"}.lang-lbl[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438"}.lang-lbl[lang=ca]:after{content:"Catal\0000E0"}.lang-lbl[lang=cs]:after{content:"\00010Ce\000161tina"}.lang-lbl[lang=da]:after{content:"Dansk"}.lang-lbl[lang=de]:after{content:"Deutsch"}.lang-lbl[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC"}.lang-lbl[lang=en]:after{content:"English"}.lang-lbl[lang=es]:after{content:"Espa\0000F1ol"}.lang-lbl[lang=et]:after{content:"Eesti"}.lang-lbl[lang=fi]:after{content:"Suomi"}.lang-lbl[lang=fr]:after{content:"Fran\0000E7ais"}.lang-lbl[lang=ga]:after{content:"Gaeilge"}.lang-lbl[lang=hi]:after{content:"\000939\00093F\000902\000926\000940"}.lang-lbl[lang=hr]:after{content:"Hrvatski"}.lang-lbl[lang=hu]:after{content:"Magyar"}.lang-lbl[lang=in]:after{content:"Bahasa\000020indonesia"}.lang-lbl[lang=is]:after{content:"\0000CDslenska"}.lang-lbl[lang=it]:after{content:"Italiano"}.lang-lbl[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA"}.lang-lbl[lang=ja]:after{content:"\0065E5\00672C\008A9E"}.lang-lbl[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4"}.lang-lbl[lang=lt]:after{content:"Lietuvi\000173"}.lang-lbl[lang=lv]:after{content:"Latvie\000161u"}.lang-lbl[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438"}.lang-lbl[lang=ms]:after{content:"Bahasa\000020melayu"}.lang-lbl[lang=mt]:after{content:"Malti"}.lang-lbl[lang=nl]:after{content:"Nederlands"}.lang-lbl[lang=no]:after{content:"Norsk"}.lang-lbl[lang=pl]:after{content:"Polski"}.lang-lbl[lang=pt]:after{content:"Portugu\0000EAs"}.lang-lbl[lang=ro]:after{content:"Rom\0000E2n\000103"}.lang-lbl[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439"}.lang-lbl[lang=sk]:after{content:"Sloven\00010Dina"}.lang-lbl[lang=sl]:after{content:"Sloven\000161\00010Dina"}.lang-lbl[lang=sq]:after{content:"Shqipe"}.lang-lbl[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438"}.lang-lbl[lang=sv]:after{content:"Svenska"}.lang-lbl[lang=th]:after{content:"\000E44\000E17\000E22"}.lang-lbl[lang=tr]:after{content:"T\0000FCrk\0000E7e"}.lang-lbl[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430"}.lang-lbl[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t"}.lang-lbl[lang=zh]:after{content:"\004E2D\006587"}.lang-lbl-en[lang=ar]:after{content:"Arabic"}.lang-lbl-en[lang=be]:after{content:"Belarusian"}.lang-lbl-en[lang=bg]:after{content:"Bulgarian"}.lang-lbl-en[lang=ca]:after{content:"Catalan"}.lang-lbl-en[lang=cs]:after{content:"Czech"}.lang-lbl-en[lang=da]:after{content:"Danish"}.lang-lbl-en[lang=de]:after{content:"German"}.lang-lbl-en[lang=el]:after{content:"Greek"}.lang-lbl-en[lang=en]:after{content:"English"}.lang-lbl-en[lang=es]:after{content:"Spanish"}.lang-lbl-en[lang=et]:after{content:"Estonian"}.lang-lbl-en[lang=fi]:after{content:"Finnish"}.lang-lbl-en[lang=fr]:after{content:"French"}.lang-lbl-en[lang=ga]:after{content:"Irish"}.lang-lbl-en[lang=hi]:after{content:"Hindi"}.lang-lbl-en[lang=hr]:after{content:"Croatian"}.lang-lbl-en[lang=hu]:after{content:"Hungarian"}.lang-lbl-en[lang=in]:after{content:"Indonesian"}.lang-lbl-en[lang=is]:after{content:"Icelandic"}.lang-lbl-en[lang=it]:after{content:"Italian"}.lang-lbl-en[lang=iw]:after{content:"Hebrew"}.lang-lbl-en[lang=ja]:after{content:"Japanese"}.lang-lbl-en[lang=ko]:after{content:"Korean"}.lang-lbl-en[lang=lt]:after{content:"Lithuanian"}.lang-lbl-en[lang=lv]:after{content:"Latvian"}.lang-lbl-en[lang=mk]:after{content:"Macedonian"}.lang-lbl-en[lang=ms]:after{content:"Malay"}.lang-lbl-en[lang=mt]:after{content:"Maltese"}.lang-lbl-en[lang=nl]:after{content:"Dutch"}.lang-lbl-en[lang=no]:after{content:"Norwegian"}.lang-lbl-en[lang=pl]:after{content:"Polish"}.lang-lbl-en[lang=pt]:after{content:"Portuguese"}.lang-lbl-en[lang=ro]:after{content:"Romanian"}.lang-lbl-en[lang=ru]:after{content:"Russian"}.lang-lbl-en[lang=sk]:after{content:"Slovak"}.lang-lbl-en[lang=sl]:after{content:"Slovenian"}.lang-lbl-en[lang=sq]:after{content:"Albanian"}.lang-lbl-en[lang=sr]:after{content:"Serbian"}.lang-lbl-en[lang=sv]:after{content:"Swedish"}.lang-lbl-en[lang=th]:after{content:"Thai"}.lang-lbl-en[lang=tr]:after{content:"Turkish"}.lang-lbl-en[lang=uk]:after{content:"Ukrainian"}.lang-lbl-en[lang=vi]:after{content:"Vietnamese"}.lang-lbl-en[lang=zh]:after{content:"Chinese"}.lang-lbl-full[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629\0000A0/\0000A0Arabic"}.lang-lbl-full[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456\0000A0/\0000A0Belarusian"}.lang-lbl-full[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438\0000A0/\0000A0Bulgarian"}.lang-lbl-full[lang=ca]:after{content:"Catal\0000E0\0000A0/\0000A0Catalan"}.lang-lbl-full[lang=cs]:after{content:"\00010Ce\000161tina\0000A0/\0000A0Czech"}.lang-lbl-full[lang=da]:after{content:"Dansk\0000A0/\0000A0Danish"}.lang-lbl-full[lang=de]:after{content:"Deutsch\0000A0/\0000A0German"}.lang-lbl-full[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC\0000A0/\0000A0Greek"}.lang-lbl-full[lang=en]:after{content:"English\0000A0/\0000A0English"}.lang-lbl-full[lang=es]:after{content:"Espa\0000F1ol\0000A0/\0000A0Spanish"}.lang-lbl-full[lang=et]:after{content:"Eesti\0000A0/\0000A0Estonian"}.lang-lbl-full[lang=fi]:after{content:"Suomi\0000A0/\0000A0Finnish"}.lang-lbl-full[lang=fr]:after{content:"Fran\0000E7ais\0000A0/\0000A0French"}.lang-lbl-full[lang=ga]:after{content:"Gaeilge\0000A0/\0000A0Irish"}.lang-lbl-full[lang=hi]:after{content:"\000939\00093F\000902\000926\000940\0000A0/\0000A0Hindi"}.lang-lbl-full[lang=hr]:after{content:"Hrvatski\0000A0/\0000A0Croatian"}.lang-lbl-full[lang=hu]:after{content:"Magyar\0000A0/\0000A0Hungarian"}.lang-lbl-full[lang=in]:after{content:"Bahasa\000020indonesia\0000A0/\0000A0Indonesian"}.lang-lbl-full[lang=is]:after{content:"\0000CDslenska\0000A0/\0000A0Icelandic"}.lang-lbl-full[lang=it]:after{content:"Italiano\0000A0/\0000A0Italian"}.lang-lbl-full[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA\0000A0/\0000A0Hebrew"}.lang-lbl-full[lang=ja]:after{content:"\0065E5\00672C\008A9E\0000A0/\0000A0Japanese"}.lang-lbl-full[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4\0000A0/\0000A0Korean"}.lang-lbl-full[lang=lt]:after{content:"Lietuvi\000173\0000A0/\0000A0Lithuanian"}.lang-lbl-full[lang=lv]:after{content:"Latvie\000161u\0000A0/\0000A0Latvian"}.lang-lbl-full[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438\0000A0/\0000A0Macedonian"}.lang-lbl-full[lang=ms]:after{content:"Bahasa\000020melayu\0000A0/\0000A0Malay"}.lang-lbl-full[lang=mt]:after{content:"Malti\0000A0/\0000A0Maltese"}.lang-lbl-full[lang=nl]:after{content:"Nederlands\0000A0/\0000A0Dutch"}.lang-lbl-full[lang=no]:after{content:"Norsk\0000A0/\0000A0Norwegian"}.lang-lbl-full[lang=pl]:after{content:"Polski\0000A0/\0000A0Polish"}.lang-lbl-full[lang=pt]:after{content:"Portugu\0000EAs\0000A0/\0000A0Portuguese"}.lang-lbl-full[lang=ro]:after{content:"Rom\0000E2n\000103\0000A0/\0000A0Romanian"}.lang-lbl-full[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439\0000A0/\0000A0Russian"}.lang-lbl-full[lang=sk]:after{content:"Sloven\00010Dina\0000A0/\0000A0Slovak"}.lang-lbl-full[lang=sl]:after{content:"Sloven\000161\00010Dina\0000A0/\0000A0Slovenian"}.lang-lbl-full[lang=sq]:after{content:"Shqipe\0000A0/\0000A0Albanian"}.lang-lbl-full[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438\0000A0/\0000A0Serbian"}.lang-lbl-full[lang=sv]:after{content:"Svenska\0000A0/\0000A0Swedish"}.lang-lbl-full[lang=th]:after{content:"\000E44\000E17\000E22\0000A0/\0000A0Thai"}.lang-lbl-full[lang=tr]:after{content:"T\0000FCrk\0000E7e\0000A0/\0000A0Turkish"}.lang-lbl-full[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430\0000A0/\0000A0Ukrainian"}.lang-lbl-full[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t\0000A0/\0000A0Vietnamese"}.lang-lbl-full[lang=zh]:after{content:"\004E2D\006587\0000A0/\0000A0Chinese"}.lang-lg:before,.lang-sm:before,.lang-xs:before{content:'\0000A0'}.lang-xs.lang-lbl,.lang-xs.lang-lbl-en,.lang-xs.lang-lbl-full{padding-left:16px}.lang-sm.lang-lbl,.lang-sm.lang-lbl-en,.lang-sm.lang-lbl-full{padding-left:24px}.lang-lg.lang-lbl,.lang-lg.lang-lbl-en,.lang-lg.lang-lbl-full{padding-left:32px}.lang-lg.lang-lbl-en:before,.lang-lg.lang-lbl-full:before,.lang-lg.lang-lbl:before,.lang-sm.lang-lbl-en:before,.lang-sm.lang-lbl-full:before,.lang-sm.lang-lbl:before,.lang-xs.lang-lbl-en:before,.lang-xs.lang-lbl-full:before,.lang-xs.lang-lbl:before{content:''}.lang-lg,.lang-lg:after{top:0;position:relative}.lang-sm{top:1px}.lang-sm:after{top:-1px}.lang-xs{top:4px}.lang-xs:after{top:-4px}.lead>.lang-lg{top:2px}.lead>.lang-lg:after{top:-2px}.lead>.lang-sm{top:6px}.lead>.lang-sm:after{top:-6px}.lead>.lang-xs{top:8px}.lead>.lang-xs:after{top:-8px}small>.lang-sm{top:-1px}small>.lang-sm:after{top:1px}small>.lang-xs{top:2px}small>.lang-xs:after{top:-2px}h1>.lang-lg{top:9px}h1>.lang-lg:after{top:-9px}h1>.lang-sm{top:12px}h1>.lang-sm:after{top:-12px}h1>.lang-xs{top:14px}h1>.lang-xs:after{top:-14px}h2>.lang-lg{top:5px}h2>.lang-lg:after{top:-5px}h2>.lang-sm{top:8px}h2>.lang-sm:after{top:-8px}h2>.lang-xs{top:10px}h2>.lang-xs:after{top:-10px}h3>.lang-lg{top:1px}h3>.lang-lg:after{top:-1px}h3>.lang-sm{top:5px}h3>.lang-sm:after{top:-5px}h3>.lang-xs{top:8px}h3>.lang-xs:after{top:-8px}h4>.lang-lg{top:-1px}h4>.lang-lg:after,h4>.lang-sm{top:1px}h4>.lang-sm:after{top:-1px}h4>.lang-xs{top:4px}h4>.lang-xs:after{top:-4px}h5>.lang-sm,h5>.lang-sm:after{top:0}h5>.lang-xs{top:2px}h5>.lang-xs:after{top:-2px}h6>.lang-sm,h6>.lang-sm:after{top:0}h6>.lang-xs{top:1px}h6>.lang-xs:after{top:-1px}.btn>.lang-sm{top:2px}.btn>.lang-sm:after{top:-2px}.btn>.lang-xs{top:4px}.btn>.lang-xs:after{top:-4px}.btn.btn-xs>.lang-sm,.btn.btn-xs>.lang-sm:after{top:0}.btn.btn-xs>.lang-xs{top:3px}.btn.btn-xs>.lang-xs:after{top:-3px}.btn.btn-sm>.lang-sm,.btn.btn-sm>.lang-sm:after{top:0}.btn.btn-sm>.lang-xs{top:3px}.btn.btn-sm>.lang-xs:after{top:-3px}.btn.btn-lg>.lang-lg{top:1px}.btn.btn-lg>.lang-lg:after{top:-1px}.btn.btn-lg>.lang-sm{top:3px}.btn.btn-lg>.lang-sm:after{top:-3px}.btn.btn-lg>.lang-xs{top:6px}.btn.btn-lg>.lang-xs:after{top:-6px} \ No newline at end of file diff --git a/data/web/css/build/008-mailcow.css b/data/web/css/build/008-mailcow.css new file mode 100644 index 00000000..5a51bfae --- /dev/null +++ b/data/web/css/build/008-mailcow.css @@ -0,0 +1,172 @@ +@font-face { + font-family: 'PT Sans'; + font-style: normal; + font-weight: 400; + src: local('PT Sans'), local('PTSans-Regular'), + url('/fonts/pt-sans-v9-cyrillic_latin-regular.woff2') format('woff2'), + url('/fonts/pt-sans-v9-cyrillic_latin-regular.woff') format('woff'); +} +@font-face { + font-family: 'PT Sans'; + font-style: normal; + font-weight: 700; + src: local('PT Sans Bold'), local('PTSans-Bold'), + url('/fonts/pt-sans-v9-cyrillic_latin-700.woff2') format('woff2'), + url('/fonts/pt-sans-v9-cyrillic_latin-700.woff') format('woff'); +} +@font-face { + font-family: 'PT Sans'; + font-style: italic; + font-weight: 400; + src: local('PT Sans Italic'), local('PTSans-Italic'), + url('/fonts/pt-sans-v9-cyrillic_latin-italic.woff2') format('woff2'), + url('/fonts/pt-sans-v9-cyrillic_latin-italic.woff') format('woff'); +} +#maxmsgsize { min-width: 80px; } +#slider1 .slider-selection { + background: #FFD700; +} +#slider1 .slider-track-high { + background: #FF4500; +} +#slider1 .slider-track-low { + background: #66CD00; +} +.striped:nth-child(odd) { + background-color: #fff; +} +.striped:nth-child(even) { + background-color: #fafafa; + border:1px solid white; +} +.btn { + text-transform: none; +} +.textarea-code { + font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; + background:transparent !important; +} +.navbar-nav { + margin: 0; +} +.navbar-fixed-bottom .navbar-collapse, +.navbar-fixed-top .navbar-collapse { + max-height: 1000px +} +.glyphicon-spin { + font-size:12px; + -webkit-animation: spin 2000ms infinite linear; + animation: spin 2000ms infinite linear; +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;} +.footable-sortable { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +/* Fix modal moving content left */ +body.modal-open { + overflow: inherit; + padding-right: inherit !important; +} +body { + font-family: "PT Sans","Helvetica Neue",Helvetica,Arial,sans-serif; +} +#mailcow-alert { + position: fixed; + bottom: 8px; + right: 25px; + min-width: 350px; + max-width: 550px; + z-index: 2000; +} +.input-group-sm .btn { margin-top: 0px !important } +legend { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none + -o-user-select: none; + user-select: none; + font-size: 12pt; +} +.navbar .navbar-brand { + padding-top: 5px; +} +.navbar .navbar-brand img { + height: 40px; +} +.mailcow-logo img { + max-width: 250px; +} +.lang-link-disabled a { + pointer-events: none; +} +.lang-link-disabled { + cursor: not-allowed; +} +.dkim-label { + margin: 0 0 2px !important; +} +.overlay { + background: #fff; + position: absolute; + z-index: 10000; + top: 0; right: 0; bottom: 0; left: 0; + opacity: 0.7; +} +nav .glyphicon { + font-size: 12px !important; +} +#top { + padding-top: 70px; +} +.bootstrap-select.btn-group .no-results { + display: none; +} +.dropdown-desc { + display: block; + padding: 3px 10px; + clear: both; + font-weight: bold; + color: #5a5a5a; + white-space: nowrap; +} +.haveibeenpwned { + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.full-width-select { + width: 100%!important; +} +.tooltip { + font-family: inherit; + font-size: 12px; +} +.progress-bar { + font-size: 12px; + line-height: 14px; +} \ No newline at end of file diff --git a/data/web/css/animate.min.css b/data/web/css/build/009-animate.min.css similarity index 100% rename from data/web/css/animate.min.css rename to data/web/css/build/009-animate.min.css diff --git a/data/web/css/numberedtextarea.min.css b/data/web/css/build/010-numberedtextarea.min.css similarity index 88% rename from data/web/css/numberedtextarea.min.css rename to data/web/css/build/010-numberedtextarea.min.css index c147b16b..133faf95 100644 --- a/data/web/css/numberedtextarea.min.css +++ b/data/web/css/build/010-numberedtextarea.min.css @@ -1 +1 @@ -div.numberedtextarea-wrapper{position:relative}div.numberedtextarea-wrapper textarea{display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.numberedtextarea-line-numbers{position:absolute;top:0;left:0;right:0;bottom:0;width:50px;border-right:none;color:rgba(0,0,0,.4);overflow:hidden}div.numberedtextarea-number{padding-right:6px;text-align:right}textarea#script_data{font-family:Monospace} \ No newline at end of file +div.numberedtextarea-wrapper{position:relative}div.numberedtextarea-wrapper textarea{display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.numberedtextarea-line-numbers{position:absolute;top:0;left:0;right:0;bottom:0;width:50px;border-right:none;color:rgba(0,0,0,.4);overflow:hidden}div.numberedtextarea-number{padding-right:6px;text-align:right} diff --git a/data/web/css/jquery.jqplot.min.css b/data/web/css/build/011-jquery.jqplot.min.css similarity index 100% rename from data/web/css/jquery.jqplot.min.css rename to data/web/css/build/011-jquery.jqplot.min.css diff --git a/data/web/css/mailcow.css b/data/web/css/mailcow.css deleted file mode 100644 index 374688a1..00000000 --- a/data/web/css/mailcow.css +++ /dev/null @@ -1,150 +0,0 @@ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 300; - src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('../fonts/SourceSansPro-Light.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 400; - src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url('../fonts/SourceSansPro-Regular.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 700; - src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('../fonts/SourceSansPro-Bold.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: italic; - font-weight: 700; - src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldIt'), url('../fonts/SourceSansPro-BoldIt.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; -} -#maxmsgsize { min-width: 80px; } -#slider1 .slider-selection { - background: #FFD700; -} -#slider1 .slider-track-high { - background: #FF4500; -} -#slider1 .slider-track-low { - background: #66CD00; -} -.striped:nth-child(odd) { - background-color: #fff; -} -.striped:nth-child(even) { - background-color: #fafafa; - border:1px solid white; -} -.btn { - text-transform: none; -} -.glyphicon-spin { - font-size:12px; - -webkit-animation: spin 2000ms infinite linear; - animation: spin 2000ms infinite linear; -} -@-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;} -.footable-sortable { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -/* Fix modal moving content left */ -body.modal-open { - overflow: inherit; - padding-right: inherit !important; -} -body { - font-size:11pt; -} -#mailcow-alert { - position: fixed; - bottom: 8px; - right: 25px; - min-width: 350px; - max-width: 550px; - z-index: 2000; -} -.input-group-sm .btn { margin-top: 0px !important } -legend { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none - -o-user-select: none; - user-select: none; - font-size: 12pt; -} -.navbar .navbar-brand { - padding-top: 5px; -} -.navbar .navbar-brand img { - height: 40px; -} -.mailcow-logo img { - max-width: 250px; -} -.lang-link-disabled a { - pointer-events: none; -} -.lang-link-disabled { - cursor: not-allowed; -} -.dkim-label { - margin: 0 0 2px !important; -} -.overlay { - background: #fff; - position: absolute; - z-index: 10000; - top: 0; right: 0; bottom: 0; left: 0; - opacity: 0.7; -} -nav .glyphicon { - font-size: 12px !important; -} -.logged-in-as { - border-left: 1px solid #E7E7E7; -} -#top { - padding-top: 70px; -} -.bootstrap-select.btn-group .no-results { - display: none; -} -.dropdown-desc { - display: block; - padding: 3px 10px; - clear: both; - font-weight: bold; - color: #5a5a5a; - white-space: nowrap; -} diff --git a/data/web/css/admin.css b/data/web/css/site/admin.css similarity index 76% rename from data/web/css/admin.css rename to data/web/css/site/admin.css index a53d721c..cadc4a3a 100644 --- a/data/web/css/admin.css +++ b/data/web/css/site/admin.css @@ -24,6 +24,11 @@ body.modal-open { overflow-y:scroll; padding-right: inherit !important; } +@media (min-width: 992px) { + .container { + width: 80%; + } +} .mass-actions-admin { user-select: none; padding:10px 0 10px 0; @@ -60,17 +65,9 @@ body.modal-open { .nav-tabs>li>a { z-index: 1; } -#settings_map { - font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; - font-size:9pt; - background:transparent; -} -.bootstrap-select { - width: auto!important; -} .table-condensed .input-sm { width: 100%!important; } -.full-width-select { - width: 100%!important; -} +.table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td { + padding: 3px; +} \ No newline at end of file diff --git a/data/web/css/debug.css b/data/web/css/site/debug.css similarity index 92% rename from data/web/css/debug.css rename to data/web/css/site/debug.css index b127d5da..0eb81bc3 100644 --- a/data/web/css/debug.css +++ b/data/web/css/site/debug.css @@ -40,5 +40,4 @@ table.footable>tbody>tr.footable-empty>td { } tbody { font-size:14px; - color:#333; } \ No newline at end of file diff --git a/data/web/css/edit.css b/data/web/css/site/edit.css similarity index 100% rename from data/web/css/edit.css rename to data/web/css/site/edit.css diff --git a/data/web/css/mailbox.css b/data/web/css/site/mailbox.css similarity index 80% rename from data/web/css/mailbox.css rename to data/web/css/site/mailbox.css index da2e96e3..80ff063b 100644 --- a/data/web/css/mailbox.css +++ b/data/web/css/site/mailbox.css @@ -8,6 +8,14 @@ table.footable>tbody>tr.footable-empty>td { .panel panel-default { overflow: visible !important; } +.table-responsive { + overflow: auto !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } +} .btn-group { width: max-content; } diff --git a/data/web/css/quarantine.css b/data/web/css/site/quarantine.css similarity index 69% rename from data/web/css/quarantine.css rename to data/web/css/site/quarantine.css index 6690ab90..5d0fa1ef 100644 --- a/data/web/css/quarantine.css +++ b/data/web/css/site/quarantine.css @@ -35,3 +35,17 @@ table.footable>tbody>tr.footable-empty>td { .inputMissingAttr { border-color: #FF4136; } +.dot-danger { + height: 10px; + width: 10px; + background-color: #ff4136; + border-radius: 50%; + display: inline-block; +} +.dot-neutral { + height: 10px; + width: 10px; + background-color: #d4d4d4; + border-radius: 50%; + display: inline-block; +} \ No newline at end of file diff --git a/data/web/css/user.css b/data/web/css/site/user.css similarity index 91% rename from data/web/css/user.css rename to data/web/css/site/user.css index e24bebe4..aa3ddad7 100644 --- a/data/web/css/user.css +++ b/data/web/css/site/user.css @@ -36,4 +36,7 @@ table.footable>tbody>tr.footable-empty>td { white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word; +} +body { + overflow-y:scroll; } \ No newline at end of file diff --git a/data/web/debug.php b/data/web/debug.php index 646aaa19..bbeda91c 100644 --- a/data/web/debug.php +++ b/data/web/debug.php @@ -1,16 +1,21 @@ <?php -require_once "inc/prerequisites.inc.php"; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { -require_once "inc/header.inc.php"; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; - +if (strtolower(getenv('SKIP_SOLR')) == 'n') { + $solr_status = solr_status(); +} +else { + $solr_status = false; +} ?> <div class="container"> <ul class="nav nav-tabs" role="tablist"> - <li role="presentation" class="active"><a href="#tab-containers" aria-controls="tab-containers" role="tab" data-toggle="tab">Containers & System</a></li> - <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Logs + <li role="presentation" class="active"><a href="#tab-containers" aria-controls="tab-containers" role="tab" data-toggle="tab"><?=$lang['debug']['system_containers'];?></a></li> + <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['debug']['logs'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> <li role="presentation"><span class="dropdown-desc"><?=$lang['debug']['in_memory_logs'];?></span></li> @@ -22,6 +27,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <li role="presentation"><a href="#tab-watchdog-logs" aria-controls="tab-watchdog-logs" role="tab" data-toggle="tab">Watchdog</a></li> <li role="presentation"><a href="#tab-acme-logs" aria-controls="tab-acme-logs" role="tab" data-toggle="tab">ACME</a></li> <li role="presentation"><a href="#tab-api-logs" aria-controls="tab-api-logs" role="tab" data-toggle="tab">API</a></li> + <li role="presentation"><a href="#tab-api-rl" aria-controls="tab-api-rl" role="tab" data-toggle="tab">Ratelimits</a></li> <li role="presentation"><span class="dropdown-desc"><?=$lang['debug']['external_logs'];?></span></li> <li role="presentation"><a href="#tab-rspamd-history" aria-controls="tab-rspamd-history" role="tab" data-toggle="tab">Rspamd</a></li> <li role="presentation"><span class="dropdown-desc"><?=$lang['debug']['static_logs'];?></span></li> @@ -35,13 +41,13 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <div class="tab-content" style="padding-top:20px"> <div class="debug-log-info"><?=sprintf($lang['debug']['log_info'], getenv('LOG_LINES') + 1);?></div> <?php - $exec_fields = array('cmd' => 'df', 'dir' => '/var/vmail'); + $exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail'); $vmail_df = explode(',', json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true)); ?> <div role="tabpanel" class="tab-pane active" id="tab-containers"> <div class="panel panel-default"> <div class="panel-heading"> - <h3 class="panel-title">Disk usage</h3> + <h3 class="panel-title"><?=$lang['debug']['disk_usage'];?></h3> </div> <div class="panel-body"> <div class="row"> @@ -59,7 +65,36 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; </div> <div class="panel panel-default"> <div class="panel-heading"> - <h3 class="panel-title">Container information</h3> + <h3 class="panel-title"><?=$lang['debug']['solr_status'];?></h3> + </div> + <div class="panel-body"> + <div class="row"> + <div class="col-sm-3"> + <p><img class="img-responsive" alt="Solr Logo" width="128px" src=" data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABlCAYAAAAI2qyuAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAXEUAAFxFAbktYiwAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuNWRHWFIAABv7SURBVHhe7V0JuBxVlX4JMy6jM4qOC4rp6qWq+jW+ruoXkIjoS1d3EhYXRvNkFHFBYECUAXGAASEOyiLIrigOEpDoACIqwogLiwMSkIghYF4viSFhiUGWCAQIyXuZ/9w+3V3VdXtfee/+33e+16/uqXtv1T2n7j33nnvukIJCP7BjbOzvsgt0O5cyjsymzG9mU8YvcylzIucYT2Ydczt+7xDkGFuR/hf8XplxzHG6d20qauSSkfCSJUOzRWYKCtMBE3uZ/5hxjE9A4K+FEmwuKUFj9Nd1Y9brdwwNzco6xr10DXk8DAX77kTaXEQKx8UoKLz8AGE+D73BcxVC3zBl09HPUz6ZpOHI0kEboXhnrU3H5ogCFRQGHSvmzv17+stf/ZaVA71EppgXFO0Xcp4CYai2LZeKLs2m9BDxKygMHEg4Mynz+kzK2IP+X52KvlEmzI1SPml+kPLB8CyOXmJKxuMnYyvoG7l9Iv9E9yoo9B3Xjg/thC/8cegtnsffFyGcr6TrBWNcJsQNkGPeIjIHkO8yKU9teiSXiryfs1BQ6A8eWhTdBV/3W4qCid/3c9IQ9QAugW2YoGSTeSc6Snn8KTkcKAyf5Ly1iHudi9eNBV4lKqSg0Evk0tF5EN7HPILpmN/l5CEMdY71pDVKjnkFZ0HDqwulPE1QJmneM5E238ZZKih0H3ln+AB8oV/wC6TxOWYhBfmGP702offYknX0t9P9Dy6MvaE9I99Fjrk+50RiomIKCt0EBO7jENzyop6LVqNXYTbiu6YyvR5NpMz/4tuH8PvLMp6WyTFO5awVFLoDfIkXQ9CkygHatuL9c/+BWYfQwyyX8FQlKN1jGxfEX0P3blg879XoTTbJ+JolskUySeNEUae0PkxEvxUUOgoMfd4rZqkkQkgEAV/FrALgfVzGV41gb3yWbx3KYKgm42mWqKfLpqKHU54Fg9/cgHqtp8kFUZCCQidAK9UQrpoCD2H8HrMPUU9SbRhWhVZeOz6+E927A39h/K+R8DRHjrGVejzKM7dP5E3IM1NMQ92WF6ejFRTawoPjsVdA0O72CJ+E0AMcxbcMkVOhjEdGYgiUNlJ861DeMcdlfM2R8SyM8gWUHy0aQllWSPguFgUqKLSDfMrYRyJcPppImnvxLSTkSRmPlBzjRr5NAMr4eylfo+QYT2A4+C7Ki9ZAsk70NhmfUEw8myhUQaFV0PBKJmBuwvDlpXVjY6UFOVw7pJKnCm3LObHS9Cv1JBKeZuiRfHJ4N8qLPH2hbD+V8JTJMdY/OBZ7rShcQaEV7FgyNBtj9iekAsaE9AeYXSDjRJfI+CoJBvO3+BYBKNrNMr5GCHXIToyZGuVDjpIZx7xCxucjxzhbFK6g0CogSP/rEywXQThLBjoBw5fLZHxugnJsJuOZbxnKLtQtGvbIeOsRyr9vrfPOt1A+pBzoOc6V8ckIivRiUbEUFFoCBPcsmXAVKTvfPJpZBSCgv5bxecgxjmd2gXzKvErKV4egHLevTYdex9lQXZteYMykyi4yCgp1UXT3KAJC9JFKoXJTZoHxHmYVgPBnZXxFwlDqIbcTYatOidmkccNd83Z9NWczlEubR0BBpLy1iNZ31NqIQsOgIRL/FPgzBFgmWET4gm9fySvgBLFRSuqnVSYo4L8yuwAUqmmnxIxjXFncUEWgPFHupIy3EZpwzNM4KwWF6kBvEKRZqdtc+70L43q5oQ6h/BOzCZAtIOMrEgR7OeXH7IWNVU06JaJ+F7jzyM83F1GdZbyNEnqRh4uLlQoKVTGRLIzhK/d5QwApAolfuBzzSmYRoB2FUj4QlGkqM987HAP/KTJeGdH9EOST+VaBNRjeNatg1YjWbzhbBQU5MGQS0UMyaWNvviQAwTyzUqCIILReA72GvQJl+BGzCWyA/YB8G3JKLAyfyu70hDUpcwQK+pSMvxWinomzVlDwY82C0JsLgigE5iC+LABBXOwWpiKREyOzCOTmG8fL+cwXK4MqgLdBp0Rjaz41fCDfJiDcWRzjUTl/a4Q65jl7BQU/3EpQdBEvYt0iU3MLExG+uNsedbm4E6oZ3Og9zmEWAbJxoIx1nRIhtFvI3YVvE1i9KLpLxjHXyvjbIRrCqdkshapAb1BaYIOCeJz5eEX9abdAQaFWc3IJuH69h6dAIgAcswhAYQ6U8HkICviU28eLsGr/kZ0hyPfL+Nsmx9iu7BCFqoDg/colMDfx5RIgQN4FwLTXQCdgOOTznEW+HjuFAOWq45RoPDYBG4PZBWg6GUp6l5y/daLVdDzbpVBGk4tSUPAjk6RwOQWhwdf7Qb5cAoTobLdg5dLGMZwkUFgDqexlygHgiqgRKVEQ8siTjcHsAsLtPlnb5aVZwvANBr5x+rqx2Fu5mKHMmPHP/FNBoYyCa3h5oQ2/N7vXGgj5tPFRj4Alh9/HSQIUh9edhyDH/AAnl4BeoJZT4v3r9isLLKEwvDOvlvC2RJmU+TCGeF/c5PLkLYQwMi6nHoovKSiUwTv5HigKEQRyigSekwUecG2EQvp2t4ARMguNaDFd8KSMWyuVjALKkTHs5ivxO+Ydq/Ye2ZlZBUSv5JiXyPibJsdYlU1FP7XD1aPRPviC97HxLPFAQR7iJAUFL/BV9WyOyiZ1i5ME+EsuIrRDkLJ8uYTcgujC0r2pcgA4N6A0PyjyeMgxbnIHfSgCw7ivSvmbINTldjzLfm5lpWeBjXMInsMzVYz/NzOLgoIfEJDS8CefDIv4uG5gyPQbTv8hXyoBaUcU7804RikAXBHVnBJJacjGYLYSYKu0FnwOhOfYDrpudTpSCkVURGa+kULafbL7yGBnNgUFPwqr04WACxh6lPaZF4Gv8ddFGsbwfKkECPTXhKA5xnOVHsEEKMIFRUEsEuyBi+lrziwl4Iv/yWpDsVqEe15AOd9ekx7WOasSKOQP6nYjeKT3EikFeZkgEom8skixmP/r2k3Q/ggWljP4UgklQz0dHeNLJaAHuZLSMAzzecZWOiWSkIJKgeLcgPJ9CDxNub+jR3gyl4qevsoJio1TbhSimujfRHl184RyPcu3KQwq5u0679VG0NpWJF2z/8JJPYHwyHWMZyDwV/OlEvILYxHqYSoNeAKE9DbQY7J93rjn1LIQmpMQ2C9wkgf5VHg+0mu6y7sJ5a3HcO4YWZk0M4e0E4p2UyOEsh/m29vB+E6RyGjYDCY+YASsYwzN+oYRtJfi73X4eyPoZ3rQvhZ/L0cDnwOeY00t8UG6Z2hoiTpjrg4KCmLvKBIU5AlO6hkgVCeDlvO/JYjZLsdYwf96AIVam0uah/C/JRQiJRocV8t4CcryCU7yYCIZnYsy/1YptDJCfqvy6eGDZcew0TmGtFIPnnWye2tRNmn+gbNpDqY2YhqB+IkQ/JuhCJvdDdgMobGfMjT7el2zDo9G93gjZ6/gwiAoCPlY5R3DpyAECHgpCmIRPMP1e5k9AWE9UggfnSEyX9+fL3uQGTOiULCanr34uk9hCHQreoV9K6ePiyAvZFJs2f0N0vWcVX0EAmOvigTjn4Uwr3A3WKcIDb8VtAzkmU6c6RgEBSFMpMyDZLNLlQ6KBDLKi8Ha3Cg6JUJoN1d6/hbxwMLd3gHlWS8RVkFI2w7F+NFaPr1KBvIURjnXgqR5NEpQ5tM5y+oYw0OZocQR+NI/4m6oLtIkhOCqUCj+Zq7CjMagKAgNVXYc7nUTqYZqoTzFUCdl/qVyTaWI3N6RN6HnWC0TVtHjpMxLyO5hdh/W7z2yM5TvXFDVuMHNUDYZ/RhnLcdw2NpND1p/cDdQJWGYtR22xWr8vUYPJk6LBOJHmEF7cVSzFpphK4nr6TBsDj1gfRrDsRNwz6XohX6HfLdU5uUhzd5EeXBVZiwGRUHahVgFx5c/vzAsFXAy9KEcPqdFKMaTGMZ9dc2C6h9MWg0nQx+8NWN2NUu05ZiL8ANCPg7BlwoxhP5p/P0uGeaRyLtaOiSRpiuFAmn2+aQMlWUI0mjmxj6Ub5mRmC4KQj5VsvUQgvD9gj3hFk78/xB6jKNrRTskpYMNcgAUo2bklFYo45gbqtk2QwZsDTTGlLthBIlhlnXU3F384852QMoiytSsh31loh5kxDPrjMN0UZBqoJkwCGRp3wiU4n6yd9yBImQQs1wp8/bifR0nx7yUi/IirI18sDBscgupNUlTtPF4OaRLNxB/S/w1EIBzC+V5hGJ7JBj3eYLOBExnBSnMdhnfg6BTAIZbaLdg1a82Iw8jHsL7ffC3HNanESJ/LS6yDGNOPOiftrWep3ULZukJxJpK0H7OXQ8IxjM0vcwsMwbTWUHyKf0sCPo1E2Pm7nypKoSNkjK+Rsa6TKA7SajT47IZu1lQjl9VNMbWfhnKEc0eI+V01wdDvPt67WrRb0xXBaHTZnPJ3TwboWSgoVY+qR8GxdgoE+ZuEG035uLLMEKJlLshiCCk/87JfYEZtD/uqZNm5aPh0bmcPCMw3W2QWshgyAXFKO1L6RTxkG4T8qYtwT/OOcY55KKC3x9ZkzZ3l7nawzC3r/c2hEW7qfruCoJeYyno50bI3hf/zjjXlJmoIGsWRN8JO+MXbqFujjAMc8y1UIDb8HspeoTToACH0h4V2sjljgXcEDBseS2E8EV3Q0QGZw2iptE23TGTFOTBsdhbs0nzvyHYVc80LHz9yVvXXJlNGTeA//y8ox8HA//A1anou8m5kmbGOMumUHWCgBbz3I0AZSEvxhktmIOCmaAghYM+zZMg+M9guLMVtB49wB15x1iWSRtnkP8WzSpRz1I8HrpdkBLRPpGsE6VA17Sv5TcoV36ADnoLWuEuK0jQvpyTFPqMmaAgFPCBfLNoIXFFg+4szYCUgcL4YJj1CSjiebQoib+VnsJ/rRrFBC/+Ik8jBOPHcZJCnzGTjfRWQLNeHDDioGxKPx/KcHsxCEMtQg9V3e/KCFpXeBohYNd20poBGMdXJxqMG7QGZGr20REtfooesL5Cbv6RgPUZXUu8zzDmdj1uklKQ6qAYW3TwJ3qDT2N4dCGGSndimNZ0dHcM4f6Hs5QDNscyTyMEEh/mpBkFWsk3g9ZBeB8/9i+YSmkK9tsEhPZcnn7uuN3WDQWJBO3FyOvuIpG3NifVw6xhLRGIzME7ClrnmIWNcLdRHnrQuh3pXZtlJGWg6IprUvqncmnjIgj2negdOrFouJrOT+di5MADXgoqN7yWOJiTZgTItR7PfTaInDDL76FZ0ux7IsEEucR0TFG6oSAQ5s+788THoOa+B+opjVD8RJS9ynOfi5Dn80uWdGaHKLnLk0t8Nq0flqP94465HNT5FXTHeGJ1KmpwsdUBm+Msz8Nq9pc4aVpjLr5KRsg6XtesZ9zP3wG6VQ/sNszFtIV+Kkhh+t86A71F7a0JoHYURNgNwitXBIW7G387sp+jJlGk+Ipg2FUBAfm3igdeyknTFhhGDuM5a+11mYIwrscQ6ic0nMA7+rIRsk/EfafBLrkM/9+L9K2S+5isF0DHoqi2epN+KYgZsOaD7yEPXw3qQA8yi06yyiajH86n9K9AiG/CEGoj7Aq5gLdBUMAtFBOYy60PjEF39zysZq3H5Wm7DmIErAMg+M+6n7lEmrUBdsjJ4XCCNvbUfAf8hf0o7rsZ5N8eANJD9nXtbA/oh4IYwcQX/R7dLtJs2pawVOQTju+jByw7FprrOaatE6CFO/LZghH9IQyHTsVX/0YIeFs+WVC4pzNj3hOz6oK21eKBPeNvchbk5GmFQm/pdacnguD9FX8/R8MuZm0K4fDIKITql5X5EuH68tiu897ArE2hxwoyC/mf50ljwvWt+LBcYYZtGpb09eO5Hkoz4ZgfEEqTMn4GwX+EVtgrlcFHjpGjgHGcTXPAV2FpxQu5BZenVS8C5TgMz+b70uP6dZGI/SZmawez0GN8DArhM/YhlPe2svuylwqC3zRRUb5eTr+OtkKImwcTs2hLbjYV3R/Dp5PR0/wUfze4lQP//3T9/t5g2E1B10b3lLycabPdVQ/Y+0G4vMMGzdrWDY9lfc7uISjd/Z6yCnQz9dbM1hB6pSAUS8BzTVy3/6aHEp5zzF9OEEqT1PeDwizGv+1/7PFCbqh4SS/Qghgnv2yh67uH8CyerzoE+CU9EO/aeg/1FhjK3e4uU5QbtM9ilobQIwX5Fb0P9zXwPBoMjnpOdeoGyM7IJaMLs46xDMOkSzBkOoW23NKRzrR7sPLAnb6CDC28LM8CGRrkWSOQ8Byc+HICrYgbWvx3Fc9EW3g/wixdAxnxELzfu8sGTVKwCmapi54oSCVRRJlgvP76QAfB8XovhIJs9Q6NyLPXeAi/f5tPmVdRzN1M0jgy4xj75pxIjBb5KCQRZ9N90FcVL6linG5NRoLWOdTgzPaygVQYQtYJnNx1vDO451t41qdUPuqUp0B8zFITvVYQ6knYEO8LaOEOCnF9o9O7ZJzDvtgMRbor6+j/wtl0FzTNh5clmba0NqKHOb4XPkidQGzX2BtQ76fczwAB+AWSejr5wNuHPTNn5EHNyTXRawUxNevLzNZX5NKRMfQo98qUokjkdwXl+Dl+H7I2HXod39ob4GUdWjkuLRIaaSsU5SY0+rG0hjKoPYseTJxZUe9nIpGRXTm5p0AvcrG7LqCnGpnV6qmCaPYDzU4idBMU9YRc1KEo69BTPJ6lU3Md88pMKvrFtWljb1lghZ4iHEi8Gw2Slb5ML01BYTCMsH4LI/RqNMAluHY2rp2O31+BDXAKudCbgfghwjsWSsWhRbs2dhRGsmb9zVPPBr/a3UAgYL2ehNtdH12L13Xp6aWC4Lo0mHS/US8UUF9Bh7agUb6E3oQW0nwvtS0iAdbse4xg/DvkQt7JrzuU9ShvWfYm8tbl5L6AFNRTp6C1BpdrfiR6qCB/RNK0WvfqKchdAkpyGBr1LrxM30p0h4iiKN5LaxNkP3DRLQH5eGaPIBTSE4x6Ce5FPPG+jFC8pttDDxVkRod47SjMdyTeRsGoIYTfxstegb+d9oYFkQepdVEssIfnnOxGQHsWKvKa7IavUCvQtcRl7rrh3fljMLnQCwXB/1tM039KlELnMDsOm2JYi+8Z1uwPmSHr0xDKo0wtfjSdKBWhma+QvQSNcRFslGvQyHdiuLGBBNfdUDIC/7NmSKx2N2yvwNbxrApDAAbmQHg8jydABuo2wUlS9KQH0ezGD4pR6B12LTT+HjSkgsLcBPKEHvIQ0kOhuQ1N5UEIv+++Vw+OnMRJfUcsFnsFhNztSTxZa9q8Nwoi3w+iMGAYmTOys67FvwAh+LOnAYsUsFY2clQbeB903zdo7jIQyF+768dB8aRQCqLgg9jpR7NQ0n3h1l21VqHFvZ4geNakae41UONrPBcddFp+poB1DCf5oBREoSpEtPmgfZ+nMQv0HWbxgfeXl3ghUD09NrkRROhIO3cdQ/Z5nOSDUhCFmqCvv65Zd3gaNGhPVdvQJU7g9fBaKzlpYKCHhL9buY6avYyTfFAKolAXBdvEyrsbFYJyJyd7EAnEEx6+oJyvnzC1+CJ3HSl8Dif5oBREoSGE59h7oTErHCgTcU4uga55eDS7tcPguwgjYO/rrqNSEIWOAML+c3fDoqF9q+O6PkKbo8o8mvUoJw0MaFuuu46gqlFklIIoNAxDix/oadig9VtOKqGwk6/c00AYttN6CycPBMTCqes5UMczOckHpSAKDYPcTtwNC/viKU5yYzZdd/OR9zCnDQRQp8vd9cOQ60hO8kEpiEIzmI0hk3ttZErmQ0Q9i4sHZB3FSQMBDBU9C5kUpI2TfFAKotAUIOye2SxynOSkEiBEnthO6FF+wkl9R3TO6C6oU3kIqFkv1dpwphREoSlAQf7kblzy3OWkEiAA+7t5IFTPDYodEtWsw911A93NSVIoBVFoCmjcR92NKwv4Vti74t13Yc6xDuLkvgIK7gkFhN7tPzlJCqUgCg2DegE07rZy41ovVdtDjbQfuIUAgnUHJ/UNw2FrN9TFvZYzxTGAq0IpiELDoN137obF1zfDST4YoVHHzUsUDSfezcl9AQSzwg1fhHetCaUgAwLybepFJL12gF7hQk/D1j5slGa8POE/9WD8Vlzvy75rCLaF+ns2iOkhez9OrgqlIAOAsJY4WLiIa/amMIYBfHmgQF66EHjP1t560REjQXvczU+kB+I9jzUrojsW9vK760LGeV1lVQrSZ0ApTsVLKo+LNetx/N2DkwcFs1CvH5XqKMjaSLvzOL0aZlUKJglYr2NjkSHurgNoSg+OvpeTa0IpSJ8BBaG94uWXJcjaMkhRvSmmVmUdaZsuJ9cEHe4CgfAGZobBTuGMmKWrQC+2AALomlggsq7g5LpQCjIAwAs72fPCCkQnuH47Hu9vLCnagkt1cdcN9V3RTARA2UcAz3b1+ND4TszSFaCcPSB8nuB1KHcdhf9hlrpQCjIgwIv6LK3qel6ceHn2WjYme2rc0rZalF0ZrpME7MlIZDTMbA2BbAAI1m8q84po1g8bGKa1BD048l6UUXmQzgsQzncxS0PwKYjc/6wpKAVpETSNipfnWYgrkhCwOkHOOgU6bgGCMOGvh7Wl1TrQEWh4Nn+emvV/MneVNjArEkgcgfdVccCnNUneyMzTMArH49GBoKW8JqNvqx+4ohaUgrQBsae7Yt+Fl6zlaPxPxmJjHQ1YLcLhhOIfhmLcKS1XszbXcuprBBQ8Dvn7I6Zo1uN6wP4YWNrqJUX+/kOIQNakGUwcwmxNA3n80Z0fnuE4TmoJSkHaxyxDSxyMF1k1Hi/StuDFXodh2WF8fl3TwhWNju5iBu1x5EHHKlcvS7MfMAJzo3xbW9B16+0Q2JXScqCctB0WbE0F1aYeCHmeA3rel69mvdju1DIJsDtPvI9nYnPsGCdLEQvEqkakVArSIVBXjsb4ptQ2qaTC9PAvhd2gWSfga/8ZCP9iI2wdEC4clXwoBPAkPWR9C79vBs9jpXurEMreijy+3mlHQ3KRp22usjIFafYaPRj/uqmNLiqEPvUY87PI10vsfZ8jQhNRwDvpkcl4b+t1Lb4n39cywuHd34E6eYLq4V0+jWunGKFEKkrnj9BmMjFlb/0Q6WtBNLEhna5XCtJh0HFceKnfx8uvmK7sDqGsbaZmX9XtY8AovjDK82yukpP1PIT9cSjsJiGY9YN3k3AupWATXFTbQLmVaymNkDSkqFKQLoGGUpGgfTZe8EbPC+4UafFHIIhn9vLIYfIGpl6toV6yAcK7+Z0Rst7D2XcSs2nWTVZmdbIm9UDCdy64UpAug2ZWCt164gx06/fgBbtnWRomNNQTuPfXES1+SiQwOm9oaEnvDmOsQOEA08QZqJN0Fq8OPY8v/LUYVjZ8OGdrWDLbLCya+s5h95Nok7vRC7+fby5BKUiPQeE+zeDICPlGURhN9DJfZVvjcijQMnydL6PogRjSnGwER8gWSfPUal8cB2tjyWzy+kUdT4Lg3ADKg9zj/yk80yYMt+7E81xAdlavQ5vSDkTU6aOgM+g4BROE+pyH9/wfZO+R9wC1CbP7QDOVtE+/SDRxwUkKHgwN/T/fvy7K4dvMgwAAAABJRU5ErkJggg==" /></p> + </div> + <div class="col-sm-9"> + <?php + if ($solr_status !== false): + ?> + <p><?=$lang['debug']['solr_uptime'];?>: ~<?=round($solr_status['uptime'] / 1000 / 60 / 60);?>h</p> + <p><?=$lang['debug']['solr_started_at'];?>: <?=$solr_status['startTime'];?></p> + <p><?=$lang['debug']['solr_last_modified'];?>: <?=$solr_status['index']['lastModified'];?></p> + <p><?=$lang['debug']['solr_size'];?>: <?=$solr_status['index']['size'];?></p> + <p><?=$lang['debug']['solr_docs'];?>: <?=$solr_status['index']['numDocs'];?></p> + <?php + else: + ?> + <p><?=$lang['debug']['solr_dead'];?></p> + <?php + endif; + ?> + </div> + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title"><?=$lang['debug']['containers_info'];?></h3> </div> <div class="panel-body"> <ul class="list-group"> @@ -90,7 +125,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; } ?> <small>(Started on <?=$started;?>), - <a href data-toggle="modal" data-container="<?=$container;?>" data-target="#RestartContainer">Restart</a></small> + <a href data-toggle="modal" data-container="<?=$container;?>" data-target="#RestartContainer"><?=$lang['debug']['restart_container'];?></a></small> <span class="pull-right label label-<?=($container_info['State'] !== false && !empty($container_info['State'])) ? (($container_info['State']['Running'] == 1) ? 'success' : 'danger') : 'default'; ?>"> </span> </li> <?php @@ -272,6 +307,24 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; </div> </div> + <div role="tabpanel" class="tab-pane" id="tab-api-rl"> + <div class="panel panel-default"> + <div class="panel-heading">Ratelimits <span class="badge badge-info table-lines"></span> + <div class="btn-group pull-right"> + <button class="btn btn-xs btn-default add_log_lines" data-post-process="rllog" data-table="rl_log" data-log-url="ratelimited" data-nrows="100">+ 100</button> + <button class="btn btn-xs btn-default add_log_lines" data-post-process="rllog" data-table="rl_log" data-log-url="ratelimited" data-nrows="1000">+ 1000</button> + <button class="btn btn-xs btn-default refresh_table" data-draw="draw_rl_logs" data-table="rl_log"><?=$lang['admin']['refresh'];?></button> + </div> + </div> + <div class="panel-body"> + <p class="help-block"><?=$lang['admin']['hash_remove_info'];?></p> + <div class="table-responsive"> + <table class="table table-striped table-condensed" id="rl_log"></table> + </div> + </div> + </div> + </div> + </div> <!-- /tab-content --> </div> <!-- /col-md-12 --> </div> <!-- /row --> @@ -288,9 +341,8 @@ echo "var log_pagination_size = '". $LOG_PAGINATION_SIZE . "';\n"; ?> </script> -<script src="js/footable.min.js"></script> -<script src="js/debug.js"></script> <?php +$js_minifier->add('/web/js/site/debug.js'); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; } else { diff --git a/data/web/edit.php b/data/web/edit.php index c4903c28..9aa79269 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -1,11 +1,11 @@ <?php -require_once("inc/prerequisites.inc.php"); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; $AuthUsers = array("admin", "domainadmin", "user"); if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role'], $AuthUsers)) { header('Location: /'); exit(); } -require_once("inc/header.inc.php"); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; ?> <div class="container"> <div class="row"> @@ -18,108 +18,51 @@ require_once("inc/header.inc.php"); <?php if (isset($_SESSION['mailcow_cc_role'])) { if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin") { - if (isset($_GET["alias"]) && - !empty($_GET["alias"])) { - $alias = html_entity_decode(rawurldecode($_GET["alias"])); - $result = mailbox('get', 'alias_details', $alias); - if (!empty($result)) { - ?> - <h4><?=$lang['edit']['alias'];?></h4> - <br /> - <form class="form-horizontal" data-id="editalias" role="form" method="post"> - <input type="hidden" value="0" name="active"> - <div class="form-group"> - <label class="control-label col-sm-2" for="address"><?=$lang['edit']['alias'];?></label> - <div class="col-sm-10"> - <input class="form-control" type="text" name="address" value="<?=htmlspecialchars($result['address']);?>" /> - </div> - </div> - <div class="form-group"> - <label class="control-label col-sm-2" for="goto"><?=$lang['edit']['target_address'];?></label> - <div class="col-sm-10"> - <textarea id="textarea_alias_goto" class="form-control" autocapitalize="none" autocorrect="off" rows="10" id="goto" name="goto" required><?= (!preg_match('/^(null|ham|spam)@localhost$/i', $result['goto'])) ? htmlspecialchars($result['goto']) : null; ?></textarea> - <div class="checkbox"> - <label><input class="goto_checkbox"id="goto_null" type="checkbox" value="1" name="goto_null" <?= ($result['goto'] == "null@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_null'];?></label> - </div> - <div class="checkbox"> - <label><input class="goto_checkbox" id="goto_spam" type="checkbox" value="1" name="goto_spam" <?= ($result['goto'] == "spam@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_spam'];?></label> - </div> - <div class="checkbox"> - <label><input class="goto_checkbox" id="goto_ham" type="checkbox" value="1" name="goto_ham" <?= ($result['goto'] == "ham@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_ham'];?></label> - </div> - </div> - </div> - <div class="form-group"> - <div class="col-sm-offset-2 col-sm-10"> - <div class="checkbox"> - <label><input type="checkbox" value="1" name="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label> - </div> - </div> - </div> - <div class="form-group"> - <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editalias" data-item="<?=htmlspecialchars($alias);?>" data-api-url='edit/alias' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> - </div> - </div> - </form> - <?php - } - else { - ?> - <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div> - <?php - } - } - elseif (isset($_GET['domainadmin']) && - ctype_alnum(str_replace(array('_', '.', '-'), '', $_GET["domainadmin"])) && - !empty($_GET["domainadmin"]) && - $_GET["domainadmin"] != 'admin' && - $_SESSION['mailcow_cc_role'] == "admin") { - $domain_admin = $_GET["domainadmin"]; - $result = domain_admin('details', $domain_admin); - if (!empty($result)) { - ?> - <h4><?=$lang['edit']['domain_admin'];?></h4> + if (isset($_GET["alias"]) && + !empty($_GET["alias"])) { + $alias = html_entity_decode(rawurldecode($_GET["alias"])); + $result = mailbox('get', 'alias_details', $alias); + if (!empty($result)) { + ?> + <h4><?=$lang['edit']['alias'];?></h4> <br /> - <form class="form-horizontal" data-id="editdomainadmin" role="form" method="post"> + <form class="form-horizontal" data-id="editalias" role="form" method="post"> <input type="hidden" value="0" name="active"> <div class="form-group"> - <label class="control-label col-sm-2" for="username_new"><?=$lang['edit']['username'];?></label> + <label class="control-label col-sm-2" for="address"><?=$lang['edit']['alias'];?></label> <div class="col-sm-10"> - <input class="form-control" type="text" name="username_new" value="<?=htmlspecialchars($domain_admin);?>" /> + <input class="form-control" type="text" name="address" value="<?=htmlspecialchars($result['address']);?>" /> </div> </div> <div class="form-group"> - <label class="control-label col-sm-2" for="domains"><?=$lang['edit']['domains'];?></label> + <label class="control-label col-sm-2" for="goto"><?=$lang['edit']['target_address'];?></label> <div class="col-sm-10"> - <select data-live-search="true" id="domains" name="domains" multiple required> - <?php - foreach ($result['selected_domains'] as $domain): - ?> - <option selected><?=htmlspecialchars($domain);?></option> - <?php - endforeach; - foreach ($result['unselected_domains'] as $domain): - ?> - <option><?=htmlspecialchars($domain);?></option> - <?php - endforeach; - ?> - </select> + <textarea id="textarea_alias_goto" class="form-control" autocapitalize="none" autocorrect="off" rows="10" id="goto" name="goto" required><?= (!preg_match('/^(null|ham|spam)@localhost$/i', $result['goto'])) ? htmlspecialchars($result['goto']) : null; ?></textarea> + <div class="checkbox"> + <label><input class="goto_checkbox" type="checkbox" value="1" name="goto_null" <?= ($result['goto'] == "null@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_null'];?></label> + </div> + <div class="checkbox"> + <label><input class="goto_checkbox" type="checkbox" value="1" name="goto_spam" <?= ($result['goto'] == "spam@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_spam'];?></label> + </div> + <div class="checkbox"> + <label><input class="goto_checkbox" type="checkbox" value="1" name="goto_ham" <?= ($result['goto'] == "ham@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_ham'];?></label> + </div> + </div> + </div> + <hr> + <div class="form-group"> + <label class="control-label col-sm-2" for="private_"><?=$lang['edit']['private_comment'];?></label> + <div class="col-sm-10"> + <input maxlength="160" class="form-control" type="text" name="private_comment" value="<?=htmlspecialchars($result['private_comment']);?>" /> </div> </div> <div class="form-group"> - <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label> + <label class="control-label col-sm-2" for="public_comment"><?=$lang['edit']['public_comment'];?></label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password" id="password" placeholder=""> - </div> - </div> - <div class="form-group"> - <label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label> - <div class="col-sm-10"> - <input type="password" class="form-control" name="password2" id="password2"> + <input maxlength="160" class="form-control" type="text" name="public_comment" value="<?=htmlspecialchars($result['public_comment']);?>" /> </div> </div> + <hr> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> @@ -129,14 +72,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <div class="checkbox"> - <label><input type="checkbox" value="1" name="disable_tfa"> <?=$lang['tfa']['disable_tfa'];?></label> - </div> - </div> - </div> - <div class="form-group"> - <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editdomainadmin" data-item="<?=$domain_admin;?>" data-api-url='edit/domain-admin' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="editalias" data-item="<?=htmlspecialchars($alias);?>" data-api-url='edit/alias' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> </div> </div> </form> @@ -148,6 +84,159 @@ if (isset($_SESSION['mailcow_cc_role'])) { <?php } } + elseif (isset($_GET['domainadmin'])) { + $domain_admin = $_GET["domainadmin"]; + $result = domain_admin('details', $domain_admin); + if (!empty($result)) { + ?> + <h4><?=$lang['edit']['domain_admin'];?></h4> + <br /> + <form class="form-horizontal" data-id="editdomainadmin" role="form" method="post"> + <input type="hidden" value="0" name="active"> + <div class="form-group"> + <label class="control-label col-sm-2" for="username_new"><?=$lang['edit']['username'];?></label> + <div class="col-sm-10"> + <input class="form-control" type="text" name="username_new" value="<?=htmlspecialchars($domain_admin);?>" /> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="domains"><?=$lang['edit']['domains'];?></label> + <div class="col-sm-10"> + <select data-live-search="true" class="full-width-select" name="domains" multiple required> + <?php + foreach ($result['selected_domains'] as $domain): + ?> + <option selected><?=htmlspecialchars($domain);?></option> + <?php + endforeach; + foreach ($result['unselected_domains'] as $domain): + ?> + <option><?=htmlspecialchars($domain);?></option> + <?php + endforeach; + ?> + </select> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label> + <div class="col-sm-10"> + <input type="password" data-hibp="true" class="form-control" name="password" placeholder=""> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label> + <div class="col-sm-10"> + <input type="password" class="form-control" name="password2"> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="disable_tfa"> <?=$lang['tfa']['disable_tfa'];?></label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-success" data-action="edit_selected" data-api-reload-location="/admin" data-id="editdomainadmin" data-item="<?=$domain_admin;?>" data-api-url='edit/domain-admin' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + </div> + </div> + </form> + <form data-id="daacl" class="form-inline well" method="post"> + <div class="row"> + <div class="col-sm-1"> + <p class="help-block">ACL</p> + </div> + <div class="col-sm-10"> + <div class="form-group"> + <select id="da_acl" name="da_acl" size="10" multiple> + <?php + $da_acls = acl('get', 'domainadmin', $domain_admin); + foreach ($da_acls as $acl => $val): + ?> + <option value="<?=$acl;?>" <?=($val == 1) ? 'selected' : null;?>><?=$lang['acl'][$acl];?></option> + <?php + endforeach; + ?> + </select> + </div> + <div class="form-group"> + <button class="btn btn-default" data-action="edit_selected" data-id="daacl" data-item="<?=htmlspecialchars($domain_admin);?>" data-api-url='edit/da-acl' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> + </div> + </div> + </div> + </form> + <?php + } + else { + ?> + <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div> + <?php + } + } + elseif (isset($_GET['admin'])) { + $admin = $_GET["admin"]; + $result = admin('details', $admin); + if (!empty($result)) { + ?> + <h4><?=$lang['edit']['domain_admin'];?></h4> + <br /> + <form class="form-horizontal" data-id="editadmin" role="form" method="post"> + <input type="hidden" value="0" name="active"> + <div class="form-group"> + <label class="control-label col-sm-2" for="username_new"><?=$lang['edit']['username'];?></label> + <div class="col-sm-10"> + <input class="form-control" type="text" name="username_new" value="<?=htmlspecialchars($admin);?>" /> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label> + <div class="col-sm-10"> + <input type="password" data-hibp="true" class="form-control" name="password" placeholder=""> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label> + <div class="col-sm-10"> + <input type="password" class="form-control" name="password2"> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="disable_tfa"> <?=$lang['tfa']['disable_tfa'];?></label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-success" data-action="edit_selected" data-api-reload-location="/admin" data-id="editadmin" data-item="<?=$admin;?>" data-api-url='edit/admin' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + </div> + </div> + </form> + <?php + } + else { + ?> + <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div> + <?php + } + } elseif (isset($_GET['domain']) && is_valid_domain_name($_GET["domain"]) && !empty($_GET["domain"])) { @@ -161,11 +250,12 @@ if (isset($_SESSION['mailcow_cc_role'])) { <form data-id="editdomain" class="form-horizontal" role="form" method="post"> <input type="hidden" value="0" name="active"> <input type="hidden" value="0" name="backupmx"> + <input type="hidden" value="0" name="gal"> <input type="hidden" value="0" name="relay_all_recipients"> <div class="form-group"> <label class="control-label col-sm-2" for="description"><?=$lang['edit']['description'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="description" id="description" value="<?=htmlspecialchars($result['description']);?>"> + <input type="text" class="form-control" name="description" value="<?=htmlspecialchars($result['description']);?>"> </div> </div> <?php @@ -174,31 +264,31 @@ if (isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="aliases"><?=$lang['edit']['max_aliases'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="aliases" id="aliases" value="<?=intval($result['max_num_aliases_for_domain']);?>"> + <input type="number" class="form-control" name="aliases" value="<?=intval($result['max_num_aliases_for_domain']);?>"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="mailboxes"><?=$lang['edit']['max_mailboxes'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="mailboxes" id="mailboxes" value="<?=intval($result['max_num_mboxes_for_domain']);?>"> + <input type="number" class="form-control" name="mailboxes" value="<?=intval($result['max_num_mboxes_for_domain']);?>"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="maxquota"><?=$lang['edit']['max_quota'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="maxquota" id="maxquota" value="<?=intval($result['max_quota_for_mbox'] / 1048576);?>"> + <input type="number" class="form-control" name="maxquota" value="<?=intval($result['max_quota_for_mbox'] / 1048576);?>"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="quota"><?=$lang['edit']['domain_quota'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="quota" id="quota" value="<?=intval($result['max_quota_for_domain'] / 1048576);?>"> + <input type="number" class="form-control" name="quota" value="<?=intval($result['max_quota_for_domain'] / 1048576);?>"> </div> </div> <div class="form-group"> - <label class="control-label col-sm-2" for="quota">Relayhost</label> + <label class="control-label col-sm-2" for="quota"><?=$lang['edit']['relayhost'];?></label> <div class="col-sm-10"> - <select data-live-search="true" name="relayhost" id="relayhost" class="form-control"> + <select data-live-search="true" name="relayhost" class="form-control"> <?php foreach ($rlyhosts as $rlyhost) { ?> @@ -224,6 +314,14 @@ if (isset($_SESSION['mailcow_cc_role'])) { <?php } ?> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="gal" <?=(isset($result['gal_int']) && $result['gal_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['gal'];?></label> + <small class="help-block"><?=$lang['edit']['gal_info'];?></small> + </div> + </div> + </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> @@ -233,7 +331,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editdomain" data-item="<?=$domain;?>" data-api-url='edit/domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="editdomain" data-item="<?=$domain;?>" data-api-url='edit/domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> </div> </div> </form> @@ -255,18 +353,18 @@ if (isset($_SESSION['mailcow_cc_role'])) { <hr> <form data-id="domratelimit" class="form-inline well" method="post"> <div class="form-group"> - <label class="control-label">Ratelimit</label> - <input name="rl_value" id="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled"> + <label class="control-label"><?=$lang['acl']['ratelimit'];?></label> + <input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" autocomplete="off" class="form-control" placeholder="disabled"> </div> <div class="form-group"> - <select name="rl_frame" id="rl_frame" class="form-control"> + <select name="rl_frame" class="form-control"> <option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option> <option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option> <option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option> </select> </div> <div class="form-group"> - <button class="btn btn-default" id="edit_selected" data-id="domratelimit" data-item="<?=$domain;?>" data-api-url='edit/rl-domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> + <button data-acl="<?=$_SESSION['acl']['ratelimit'];?>" class="btn btn-default" data-action="edit_selected" data-id="domratelimit" data-item="<?=$domain;?>" data-api-url='edit/rl-domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> </div> </form> <hr> @@ -278,17 +376,17 @@ if (isset($_SESSION['mailcow_cc_role'])) { <table class="table table-striped table-condensed" id="wl_policy_domain_table"></table> </div> <div class="mass-actions-user"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> - <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <a class="btn btn-sm btn-danger" data-action="delete_selected" data-id="policy_wl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> </div> </div> <form class="form-inline" data-id="add_wl_policy_domain"> - <div class="input-group"> - <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required> + <div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> + <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required> <span class="input-group-btn"> - <button class="btn btn-default" id="add_item" data-id="add_wl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"wl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_wl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"wl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button> </span> </div> </form> @@ -300,17 +398,17 @@ if (isset($_SESSION['mailcow_cc_role'])) { <table class="table table-striped table-condensed" id="bl_policy_domain_table"></table> </div> <div class="mass-actions-user"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> - <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <a class="btn btn-sm btn-danger" data-action="delete_selected" data-id="policy_bl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> </div> </div> <form class="form-inline" data-id="add_bl_policy_domain"> - <div class="input-group"> - <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required> + <div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> + <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required> <span class="input-group-btn"> - <button class="btn btn-default" id="add_item" data-id="add_bl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"bl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_bl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"bl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button> </span> </div> </form> @@ -338,7 +436,15 @@ if (isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="target_domain"><?=$lang['edit']['target_domain'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="target_domain" id="target_domain" value="<?=htmlspecialchars($result['target_domain']);?>"> + <select class="full-width-select" data-live-search="true" id="addSelectDomain" name="target_domain" required> + <?php + foreach (mailbox('get', 'domains') as $domain): + ?> + <option <?=($result['target_domain'] != $domain) ?: 'selected';?>><?=htmlspecialchars($domain);?></option> + <?php + endforeach; + ?> + </select> </div> </div> <div class="form-group"> @@ -350,25 +456,25 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editaliasdomain" data-item="<?=$alias_domain;?>" data-api-url='edit/alias-domain' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="editaliasdomain" data-item="<?=$alias_domain;?>" data-api-url='edit/alias-domain' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> </div> </div> </form> <hr> <form data-id="domratelimit" class="form-inline well" method="post"> <div class="form-group"> - <label class="control-label">Ratelimit</label> - <input name="rl_value" id="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled"> + <label class="control-label"><?=$lang['acl']['ratelimit'];?></label> + <input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" autocomplete="off" class="form-control" placeholder="disabled"> </div> <div class="form-group"> - <select name="rl_frame" id="rl_frame" class="form-control"> + <select name="rl_frame" class="form-control"> <option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option> <option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option> <option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option> </select> </div> <div class="form-group"> - <button class="btn btn-default" id="edit_selected" data-id="domratelimit" data-item="<?=$alias_domain;?>" data-api-url='edit/rl-domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> + <button class="btn btn-default" data-action="edit_selected" data-id="domratelimit" data-item="<?=$alias_domain;?>" data-api-url='edit/rl-domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> </div> </form> <?php @@ -403,10 +509,11 @@ if (isset($_SESSION['mailcow_cc_role'])) { <input type="hidden" value="default" name="sender_acl"> <input type="hidden" value="0" name="active"> <input type="hidden" value="0" name="force_pw_update"> + <input type="hidden" value="0" name="sogo_access"> <div class="form-group"> <label class="control-label col-sm-2" for="name"><?=$lang['edit']['full_name'];?>:</label> <div class="col-sm-10"> - <input type="text" class="form-control" name="name" id="name" value="<?=htmlspecialchars($result['name'], ENT_QUOTES, 'UTF-8');?>"> + <input type="text" class="form-control" name="name" value="<?=htmlspecialchars($result['name'], ENT_QUOTES, 'UTF-8');?>"> </div> </div> <div class="form-group"> @@ -414,13 +521,14 @@ if (isset($_SESSION['mailcow_cc_role'])) { <br /><span id="quotaBadge" class="badge">max. <?=intval($result['max_new_quota'] / 1048576)?> MiB</span> </label> <div class="col-sm-10"> - <input type="number" name="quota" id="quota" id="destroyable" style="width:100%" min="1" max="<?=intval($result['max_new_quota'] / 1048576);?>" value="<?=intval($result['quota']) / 1048576;?>" class="form-control"> + <input type="number" name="quota" style="width:100%" min="0" max="<?=intval($result['max_new_quota'] / 1048576);?>" value="<?=intval($result['quota']) / 1048576;?>" class="form-control"> + <small class="help-block">0 = โ</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="sender_acl"><?=$lang['edit']['sender_acl'];?>:</label> <div class="col-sm-10"> - <select data-live-search="true" data-width="100%" style="width:100%" id="sender_acl" name="sender_acl" size="10" multiple> + <select data-live-search="true" data-width="100%" style="width:100%" id="editSelectSenderACL" name="sender_acl" size="10" multiple> <?php $sender_acl_handles = mailbox('get', 'sender_acl_handles', $mailbox); @@ -474,13 +582,13 @@ if (isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password" id="password" placeholder="<?=$lang['edit']['unchanged_if_empty'];?>"> + <input type="password" data-hibp="true" class="form-control" name="password" placeholder="<?=$lang['edit']['unchanged_if_empty'];?>"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password2" id="password2"> + <input type="password" class="form-control" name="password2"> </div> </div> <div class="form-group"> @@ -500,7 +608,15 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editmailbox" data-item="<?=htmlspecialchars($result['username']);?>" data-api-url='edit/mailbox' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="sogo_access" <?=($result['attributes']['sogo_access']=="1") ? "checked" : null;?>> <?=$lang['edit']['sogo_access'];?></label> + <small class="help-block"><?=$lang['edit']['sogo_access_info'];?></small> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-success" data-action="edit_selected" data-id="editmailbox" data-item="<?=htmlspecialchars($result['username']);?>" data-api-url='edit/mailbox' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> </div> </div> </form> @@ -508,21 +624,45 @@ if (isset($_SESSION['mailcow_cc_role'])) { <form data-id="mboxratelimit" class="form-inline well" method="post"> <div class="row"> <div class="col-sm-1"> - <p class="help-block">Ratelimit</p> + <p class="help-block"><?=$lang['acl']['ratelimit'];?></p> </div> <div class="col-sm-10"> <div class="form-group"> - <input name="rl_value" id="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled"> + <input name="rl_value" type="number" autocomplete="off" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled"> </div> <div class="form-group"> - <select name="rl_frame" id="rl_frame" class="form-control"> + <select name="rl_frame" class="form-control"> <option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option> <option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option> <option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option> </select> </div> <div class="form-group"> - <button class="btn btn-default" id="edit_selected" data-id="mboxratelimit" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/rl-mbox' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> + <button class="btn btn-default" data-action="edit_selected" data-id="mboxratelimit" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/rl-mbox' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> + </div> + </div> + </div> + </form> + <form data-id="useracl" class="form-inline well" method="post"> + <div class="row"> + <div class="col-sm-1"> + <p class="help-block">ACL</p> + </div> + <div class="col-sm-10"> + <div class="form-group"> + <select id="user_acl" name="user_acl" size="10" multiple> + <?php + $user_acls = acl('get', 'user', $mailbox); + foreach ($user_acls as $acl => $val): + ?> + <option value="<?=$acl;?>" <?=($val == 1) ? 'selected' : null;?>><?=$lang['acl'][$acl];?></option> + <?php + endforeach; + ?> + </select> + </div> + <div class="form-group"> + <button class="btn btn-default" data-action="edit_selected" data-id="useracl" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/user-acl' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button> </div> </div> </div> @@ -541,19 +681,19 @@ if (isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="hostname"><?=$lang['add']['hostname'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="hostname" id="hostname" value="<?=htmlspecialchars($result['hostname'], ENT_QUOTES, 'UTF-8');?>" required> + <input type="text" class="form-control" name="hostname" value="<?=htmlspecialchars($result['hostname'], ENT_QUOTES, 'UTF-8');?>" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="username" id="username" value="<?=htmlspecialchars($result['username'], ENT_QUOTES, 'UTF-8');?>"> + <input type="text" class="form-control" name="username" value="<?=htmlspecialchars($result['username'], ENT_QUOTES, 'UTF-8');?>"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password" id="password" value="<?=htmlspecialchars($result['password'], ENT_QUOTES, 'UTF-8');?>"> + <input type="password" data-hibp="true" class="form-control" name="password" value="<?=htmlspecialchars($result['password'], ENT_QUOTES, 'UTF-8');?>"> </div> </div> <div class="form-group"> @@ -565,7 +705,60 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editrelayhost" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/relayhost' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="editrelayhost" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/relayhost' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + </div> + </div> + </form> + <?php + } + else { + ?> + <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div> + <?php + } + } + elseif (isset($_GET['transport']) && is_numeric($_GET["transport"]) && !empty($_GET["transport"])) { + $transport = intval($_GET["transport"]); + $result = transport('details', $transport); + if (!empty($result)) { + ?> + <h4><?=$lang['edit']['resource'];?></h4> + <form class="form-horizontal" role="form" method="post" data-id="edittransport"> + <input type="hidden" value="0" name="active"> + <div class="form-group"> + <label class="control-label col-sm-2" for="destination"><?=$lang['add']['destination'];?></label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="destination" value="<?=htmlspecialchars($result['destination'], ENT_QUOTES, 'UTF-8');?>" required> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="nexthop"><?=$lang['edit']['nexthop'];?></label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="nexthop" value="<?=htmlspecialchars($result['nexthop'], ENT_QUOTES, 'UTF-8');?>" required> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?></label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="username" value="<?=htmlspecialchars($result['username'], ENT_QUOTES, 'UTF-8');?>"> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label> + <div class="col-sm-10"> + <input type="password" data-hibp="true" class="form-control" name="password" value="<?=htmlspecialchars($result['password'], ENT_QUOTES, 'UTF-8');?>"> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="active" <?=($result['active_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['active'];?></label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-success" data-action="edit_selected" data-id="edittransport" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/transport' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> </div> </div> </form> @@ -588,13 +781,13 @@ if (isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="description" id="description" value="<?=htmlspecialchars($result['description'], ENT_QUOTES, 'UTF-8');?>" required> + <input type="text" class="form-control" name="description" value="<?=htmlspecialchars($result['description'], ENT_QUOTES, 'UTF-8');?>" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="domain"><?=$lang['edit']['kind'];?>:</label> <div class="col-sm-10"> - <select name="kind" id="kind" title="<?=$lang['edit']['select'];?>" required> + <select name="kind" title="<?=$lang['edit']['select'];?>" required> <option value="location" <?=($result['kind'] == "location") ? "selected" : null;?>>Location</option> <option value="group" <?=($result['kind'] == "group") ? "selected" : null;?>>Group</option> <option value="thing" <?=($result['kind'] == "thing") ? "selected" : null;?>>Thing</option> @@ -604,7 +797,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="multiple_bookings_select"><?=$lang['add']['multiple_bookings'];?>:</label> <div class="col-sm-10"> - <select name="multiple_bookings_select" id="multiple_bookings_select" title="<?=$lang['add']['select'];?>" required> + <select name="multiple_bookings_select" id="editSelectMultipleBookings" title="<?=$lang['add']['select'];?>" required> <option value="0" <?=($result['multiple_bookings'] == 0) ? "selected" : null;?>><?=$lang['mailbox']['booking_0'];?></option> <option value="-1" <?=($result['multiple_bookings'] == -1) ? "selected" : null;?>><?=$lang['mailbox']['booking_lt0'];?></option> <option value="custom" <?=($result['multiple_bookings'] >= 1) ? "selected" : null;?>><?=$lang['mailbox']['booking_custom'];?></option> @@ -625,7 +818,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editresource" data-item="<?=htmlspecialchars($result['name']);?>" data-api-url='edit/resource' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="editresource" data-item="<?=htmlspecialchars($result['name']);?>" data-api-url='edit/resource' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> </div> </div> </form> @@ -671,7 +864,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editbcc" data-item="<?=$bcc;?>" data-api-url='edit/bcc' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="editbcc" data-item="<?=$bcc;?>" data-api-url='edit/bcc' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> </div> </div> </form> @@ -683,7 +876,9 @@ if (isset($_SESSION['mailcow_cc_role'])) { <?php } } - elseif (isset($_GET['recipient_map']) && !empty($_GET["recipient_map"])) { + elseif (isset($_GET['recipient_map']) && + !empty($_GET["recipient_map"]) && + $_SESSION['mailcow_cc_role'] == "admin") { $map = intval($_GET["recipient_map"]); $result = recipient_map('details', $map); if (substr($result['recipient_map_old'], 0, 1) == '@') { @@ -718,7 +913,68 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="edit_recipient_map" data-item="<?=$map;?>" data-api-url='edit/recipient_map' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="edit_recipient_map" data-item="<?=$map;?>" data-api-url='edit/recipient_map' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + </div> + </div> + </form> + <?php + } + else { + ?> + <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div> + <?php + } + } + elseif (isset($_GET['tls_policy_map']) && + !empty($_GET["tls_policy_map"]) && + $_SESSION['mailcow_cc_role'] == "admin") { + $map = intval($_GET["tls_policy_map"]); + $result = tls_policy_maps('details', $map); + if (!empty($result)) { + ?> + <h4><?=$lang['mailbox']['tls_policy_maps']?>: <?=$result['dest'];?></h4> + <br /> + <form class="form-horizontal" data-id="edit_tls_policy_maps" role="form" method="post"> + <input type="hidden" value="0" name="active"> + <div class="form-group"> + <label class="control-label col-sm-2" for="dest"><?=$lang['mailbox']['tls_map_dest'];?></label> + <div class="col-sm-10"> + <input value="<?=$result['dest'];?>" type="text" class="form-control" name="dest" id="dest"> + <small><?=$lang['mailbox']['tls_map_dest_info'];?></small> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="policy"><?=$lang['mailbox']['tls_map_policy'];?></label> + <div class="col-sm-10"> + <select class="full-width-select" name="policy" required> + <option value="none" <?=($result['policy'] != 'none') ?: 'selected';?>>none</option> + <option value="may" <?=($result['policy'] != 'may') ?: 'selected';?>>may</option> + <option value="encrypt" <?=($result['policy'] != 'encrypt') ?: 'selected';?>>encrypt</option> + <option value="dane" <?=($result['policy'] != 'dane') ?: 'selected';?>>dane-only</option> + <option value="dane-only" <?=($result['policy'] != 'dane-only') ?: 'selected';?>>dane-only</option> + <option value="fingerprint" <?=($result['policy'] != 'fingerprint') ?: 'selected';?>>fingerprint</option> + <option value="verify" <?=($result['policy'] != 'verify') ?: 'selected';?>>verify</option> + <option value="secure" <?=($result['policy'] != 'secure') ?: 'selected';?>>secure</option> + </select> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="parameters"><?=$lang['mailbox']['tls_map_parameters'];?></label> + <div class="col-sm-10"> + <input value="<?=$result['parameters'];?>" type="text" class="form-control" name="parameters" id="parameters"> + <small><?=$lang['mailbox']['tls_map_parameters_info'];?></small> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-success" data-action="edit_selected" data-id="edit_tls_policy_maps" data-item="<?=$map;?>" data-api-url='edit/tls-policy-map' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> </div> </div> </form> @@ -885,7 +1141,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editsyncjob" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/syncjob' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="editsyncjob" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/syncjob' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button> </div> </div> </form> @@ -924,7 +1180,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="script_data">Script:</label> <div class="col-sm-10"> - <textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control" rows="20" id="script_data" name="script_data" required><?=$result['script_data'];?></textarea> + <textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required><?=$result['script_data'];?></textarea> </div> </div> <div class="form-group"> @@ -936,7 +1192,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="edit_selected" data-id="editfilter" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/filter' data-api-attr='{}' href="#"><?=$lang['edit']['validate_save'];?></button> + <button class="btn btn-success" data-action="edit_selected" data-id="editfilter" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/filter' data-api-attr='{}' href="#"><?=$lang['edit']['validate_save'];?></button> </div> </div> </form> @@ -965,14 +1221,13 @@ else { <script type='text/javascript'> <?php $lang_user = json_encode($lang['user']); -echo "var lang = ". $lang_user . ";\n"; +echo "var lang_user = ". $lang_user . ";\n"; echo "var table_for_domain = '". ((isset($domain)) ? $domain : null) . "';\n"; echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n"; echo "var pagination_size = '". $PAGINATION_SIZE . "';\n"; ?> </script> -<script src="js/footable.min.js"></script> -<script src="js/edit.js"></script> <?php -require_once("inc/footer.inc.php"); +$js_minifier->add('/web/js/site/edit.js'); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; ?> diff --git a/data/web/fonts/PTSans.txt b/data/web/fonts/PTSans.txt new file mode 100644 index 00000000..1e09546e --- /dev/null +++ b/data/web/fonts/PTSans.txt @@ -0,0 +1,94 @@ +๏ปฟCopyright (c) 2010, ParaType Ltd. (http://www.paratype.com/public), +with Reserved Font Names "PT Sans" and "ParaType". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/data/web/fonts/SourceSansPro-Bold.woff2 b/data/web/fonts/SourceSansPro-Bold.woff2 deleted file mode 100644 index 89bfd796..00000000 Binary files a/data/web/fonts/SourceSansPro-Bold.woff2 and /dev/null differ diff --git a/data/web/fonts/SourceSansPro-BoldIt.woff2 b/data/web/fonts/SourceSansPro-BoldIt.woff2 deleted file mode 100644 index 83d5b491..00000000 Binary files a/data/web/fonts/SourceSansPro-BoldIt.woff2 and /dev/null differ diff --git a/data/web/fonts/SourceSansPro-Light.woff2 b/data/web/fonts/SourceSansPro-Light.woff2 deleted file mode 100644 index 75ec2c04..00000000 Binary files a/data/web/fonts/SourceSansPro-Light.woff2 and /dev/null differ diff --git a/data/web/fonts/SourceSansPro-Regular.woff2 b/data/web/fonts/SourceSansPro-Regular.woff2 deleted file mode 100644 index 6dda30cd..00000000 Binary files a/data/web/fonts/SourceSansPro-Regular.woff2 and /dev/null differ diff --git a/data/web/fonts/pt-sans-v9-cyrillic_latin-700.woff b/data/web/fonts/pt-sans-v9-cyrillic_latin-700.woff new file mode 100644 index 00000000..7d6f0c4a Binary files /dev/null and b/data/web/fonts/pt-sans-v9-cyrillic_latin-700.woff differ diff --git a/data/web/fonts/pt-sans-v9-cyrillic_latin-700.woff2 b/data/web/fonts/pt-sans-v9-cyrillic_latin-700.woff2 new file mode 100644 index 00000000..24b3daed Binary files /dev/null and b/data/web/fonts/pt-sans-v9-cyrillic_latin-700.woff2 differ diff --git a/data/web/fonts/pt-sans-v9-cyrillic_latin-italic.woff b/data/web/fonts/pt-sans-v9-cyrillic_latin-italic.woff new file mode 100644 index 00000000..b73c2004 Binary files /dev/null and b/data/web/fonts/pt-sans-v9-cyrillic_latin-italic.woff differ diff --git a/data/web/fonts/pt-sans-v9-cyrillic_latin-italic.woff2 b/data/web/fonts/pt-sans-v9-cyrillic_latin-italic.woff2 new file mode 100644 index 00000000..6d47b145 Binary files /dev/null and b/data/web/fonts/pt-sans-v9-cyrillic_latin-italic.woff2 differ diff --git a/data/web/fonts/pt-sans-v9-cyrillic_latin-regular.woff b/data/web/fonts/pt-sans-v9-cyrillic_latin-regular.woff new file mode 100644 index 00000000..b4223a1f Binary files /dev/null and b/data/web/fonts/pt-sans-v9-cyrillic_latin-regular.woff differ diff --git a/data/web/fonts/pt-sans-v9-cyrillic_latin-regular.woff2 b/data/web/fonts/pt-sans-v9-cyrillic_latin-regular.woff2 new file mode 100644 index 00000000..c1209b46 Binary files /dev/null and b/data/web/fonts/pt-sans-v9-cyrillic_latin-regular.woff2 differ diff --git a/data/web/img/languages.png b/data/web/img/languages.png new file mode 100644 index 00000000..85d9a6e5 Binary files /dev/null and b/data/web/img/languages.png differ diff --git a/data/web/inc/ajax/dns_diagnostics.php b/data/web/inc/ajax/dns_diagnostics.php index dd67dec9..8d9f74d7 100644 --- a/data/web/inc/ajax/dns_diagnostics.php +++ b/data/web/inc/ajax/dns_diagnostics.php @@ -10,9 +10,11 @@ define('state_optional', " <sup>2</sup>"); if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin"|| $_SESSION['mailcow_cc_role'] == "domainadmin")) { $domains = mailbox('get', 'domains'); -foreach(mailbox('get', 'domains') as $dn) { - $domains = array_merge($domains, mailbox('get', 'alias_domains', $dn)); +$alias_domains = array(); +foreach($domains as $dn) { + $alias_domains = array_merge($alias_domains, mailbox('get', 'alias_domains', $dn)); } +$domains = array_merge($domains, $alias_domains); if (isset($_GET['domain'])) { if (is_valid_domain_name($_GET['domain'])) { @@ -105,76 +107,80 @@ if ($_SESSION['mailcow_cc_role'] == "admin") { 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1) ); - $records[] = array( - '_'.$https_port. - '._tcp.'.$mailcow_hostname, - 'TLSA', - generate_tlsa_digest($mailcow_hostname, $https_port) - ); - $records[] = array( - '_'.$autodiscover_config['pop3']['tlsport']. - '._tcp.'.$autodiscover_config['pop3']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['tlsport'], 1) - ); - $records[] = array( - '_'.$autodiscover_config['imap']['tlsport']. - '._tcp.'.$autodiscover_config['imap']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['tlsport'], 1) - ); - $records[] = array( - '_'.$autodiscover_config['smtp']['port']. - '._tcp.'.$autodiscover_config['smtp']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['port']) - ); - $records[] = array( - '_'.$autodiscover_config['smtp']['tlsport']. - '._tcp.'.$autodiscover_config['smtp']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['tlsport'], 1) - ); - $records[] = array( - '_'.$autodiscover_config['imap']['port']. - '._tcp.'.$autodiscover_config['imap']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port']) - ); - $records[] = array( - '_'.$autodiscover_config['pop3']['port']. - '._tcp.'.$autodiscover_config['pop3']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port']) - ); - $records[] = array( - '_'.$autodiscover_config['sieve']['port']. - '._tcp.'.$autodiscover_config['sieve']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1) - ); + if (!in_array($domain, $alias_domains)) { + $records[] = array( + '_'.$https_port. + '._tcp.'.$mailcow_hostname, + 'TLSA', + generate_tlsa_digest($mailcow_hostname, $https_port) + ); + $records[] = array( + '_'.$autodiscover_config['pop3']['tlsport']. + '._tcp.'.$autodiscover_config['pop3']['server'], + 'TLSA', + generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['tlsport'], 1) + ); + $records[] = array( + '_'.$autodiscover_config['imap']['tlsport']. + '._tcp.'.$autodiscover_config['imap']['server'], + 'TLSA', + generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['tlsport'], 1) + ); + $records[] = array( + '_'.$autodiscover_config['smtp']['port']. + '._tcp.'.$autodiscover_config['smtp']['server'], + 'TLSA', + generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['port']) + ); + $records[] = array( + '_'.$autodiscover_config['smtp']['tlsport']. + '._tcp.'.$autodiscover_config['smtp']['server'], + 'TLSA', + generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['tlsport'], 1) + ); + $records[] = array( + '_'.$autodiscover_config['imap']['port']. + '._tcp.'.$autodiscover_config['imap']['server'], + 'TLSA', + generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port']) + ); + $records[] = array( + '_'.$autodiscover_config['pop3']['port']. + '._tcp.'.$autodiscover_config['pop3']['server'], + 'TLSA', + generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port']) + ); + $records[] = array( + '_'.$autodiscover_config['sieve']['port']. + '._tcp.'.$autodiscover_config['sieve']['server'], + 'TLSA', + generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1) + ); + } } $records[] = array( $domain, 'MX', $mailcow_hostname ); -$records[] = array( - 'autodiscover.'.$domain, - 'CNAME', - $mailcow_hostname -); -$records[] = array( - '_autodiscover._tcp.'.$domain, - 'SRV', - $mailcow_hostname. - ' '.$https_port -); -$records[] = array( - 'autoconfig.'.$domain, - 'CNAME', - $mailcow_hostname -); +if (!in_array($domain, $alias_domains)) { + $records[] = array( + 'autodiscover.'.$domain, + 'CNAME', + $mailcow_hostname + ); + $records[] = array( + '_autodiscover._tcp.'.$domain, + 'SRV', + $mailcow_hostname. + ' '.$https_port + ); + $records[] = array( + 'autoconfig.'.$domain, + 'CNAME', + $mailcow_hostname + ); +} $records[] = array( $domain, 'TXT', @@ -195,74 +201,76 @@ if (!empty($dkim = dkim('details', $domain))) { $dkim['dkim_txt'] ); } -$current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV); -if (count($current_records) == 0 || $current_records[0]['target'] != '') { - if ($autodiscover_config['pop3']['tlsport'] != '110') { +if (!in_array($domain, $alias_domains)) { + $current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV); + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['tlsport'] != '110') { + $records[] = array( + '_pop3._tcp.' . $domain, + 'SRV', + $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport'] + ); + } + } + else { $records[] = array( '_pop3._tcp.' . $domain, 'SRV', - $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport'] + '. 0' ); } -} -else { - $records[] = array( - '_pop3._tcp.' . $domain, - 'SRV', - '. 0' - ); -} -$current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV); -if (count($current_records) == 0 || $current_records[0]['target'] != '') { - if ($autodiscover_config['pop3']['port'] != '995') { + $current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV); + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['port'] != '995') { + $records[] = array( + '_pop3s._tcp.' . $domain, + 'SRV', + $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port'] + ); + } + } + else { $records[] = array( '_pop3s._tcp.' . $domain, 'SRV', - $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port'] + '. 0' + ); + } + if ($autodiscover_config['imap']['tlsport'] != '143') { + $records[] = array( + '_imap._tcp.' . $domain, + 'SRV', + $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport'] + ); + } + if ($autodiscover_config['imap']['port'] != '993') { + $records[] = array( + '_imaps._tcp.' . $domain, + 'SRV', + $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port'] + ); + } + if ($autodiscover_config['smtp']['tlsport'] != '587') { + $records[] = array( + '_submission._tcp.' . $domain, + 'SRV', + $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport'] + ); + } + if ($autodiscover_config['smtp']['port'] != '465') { + $records[] = array( + '_smtps._tcp.' . $domain, + 'SRV', + $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port'] + ); + } + if ($autodiscover_config['sieve']['port'] != '4190') { + $records[] = array( + '_sieve._tcp.' . $domain, + 'SRV', + $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port'] ); } -} -else { - $records[] = array( - '_pop3s._tcp.' . $domain, - 'SRV', - '. 0' - ); -} -if ($autodiscover_config['imap']['tlsport'] != '143') { - $records[] = array( - '_imap._tcp.' . $domain, - 'SRV', - $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport'] - ); -} -if ($autodiscover_config['imap']['port'] != '993') { - $records[] = array( - '_imaps._tcp.' . $domain, - 'SRV', - $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port'] - ); -} -if ($autodiscover_config['smtp']['tlsport'] != '587') { - $records[] = array( - '_submission._tcp.' . $domain, - 'SRV', - $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport'] - ); -} -if ($autodiscover_config['smtp']['port'] != '465') { - $records[] = array( - '_smtps._tcp.' . $domain, - 'SRV', - $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port'] - ); -} -if ($autodiscover_config['sieve']['port'] != '4190') { - $records[] = array( - '_sieve._tcp.' . $domain, - 'SRV', - $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port'] - ); } $record_types = array( diff --git a/data/web/inc/ajax/qitem_details.php b/data/web/inc/ajax/qitem_details.php index 801fd3d0..71d32cc9 100644 --- a/data/web/inc/ajax/qitem_details.php +++ b/data/web/inc/ajax/qitem_details.php @@ -40,6 +40,13 @@ if (!empty($_GET['id']) && ctype_alnum($_GET['id'])) { $data['text_plain'] = $mail_parser->getMessageBody('text'); // Get html content and convert to text $data['text_html'] = $html2text->convert($mail_parser->getMessageBody('html')); + if (empty($data['text_plain']) && empty($data['text_html'])) { + // Failed to parse content, try raw + $text = trim(substr($mailc['msg'], strpos($mailc['msg'], "\r\n\r\n") + 1)); + // Only return html->text + $data['text_plain'] = 'Parser failed, assuming HTML'; + $data['text_html'] = $html2text->convert($text); + } (empty($data['text_plain'])) ? $data['text_plain'] = '-' : null; // Get subject $data['subject'] = $mail_parser->getHeader('subject'); @@ -67,6 +74,9 @@ if (!empty($_GET['id']) && ctype_alnum($_GET['id'])) { } } if (isset($_GET['att'])) { + if ($_SESSION['acl']['quarantine_attachments'] == 0) { + exit(json_encode('Forbidden')); + } $dl_id = intval($_GET['att']); $dl_filename = $data['attachments'][$dl_id][0]; if (!is_dir($tmpdir . $dl_filename) && file_exists($tmpdir . $dl_filename)) { diff --git a/data/web/inc/ajax/qr_gen.php b/data/web/inc/ajax/qr_gen.php new file mode 100644 index 00000000..a39c3f64 --- /dev/null +++ b/data/web/inc/ajax/qr_gen.php @@ -0,0 +1,13 @@ +<?php +session_start(); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; +header('Content-Type: text/plain'); +if (!isset($_SESSION['mailcow_cc_role'])) { + exit(); +} + +if (isset($_GET['token']) && ctype_alnum($_GET['token'])) { + echo $tfa->getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $_GET['token']); +} + +?> diff --git a/data/web/inc/ajax/queue_manager.php b/data/web/inc/ajax/queue_manager.php new file mode 100644 index 00000000..8ae5cb35 --- /dev/null +++ b/data/web/inc/ajax/queue_manager.php @@ -0,0 +1,16 @@ +<?php +session_start(); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; +header('Content-Type: text/plain'); +if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != 'admin') { + exit(); +} +$docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq')); + +if (isset($docker_return['type']['danger'])) { + echo "Cannot load mail queue: " . $docker_return['msg']; +} +else { + echo $docker_return; +} +?> diff --git a/data/web/inc/ajax/relay_check.php b/data/web/inc/ajax/transport_check.php similarity index 64% rename from data/web/inc/ajax/relay_check.php rename to data/web/inc/ajax/transport_check.php index 34f8eceb..e956f4f2 100644 --- a/data/web/inc/ajax/relay_check.php +++ b/data/web/inc/ajax/transport_check.php @@ -4,23 +4,49 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; error_reporting(0); if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { - $relayhost_id = intval($_GET['relayhost_id']); + $transport_id = intval($_GET['transport_id']); + $transport_type = $_GET['transport_type']; if (isset($_GET['mail_from']) && filter_var($_GET['mail_from'], FILTER_VALIDATE_EMAIL)) { $mail_from = $_GET['mail_from']; } else { $mail_from = "relay@example.org"; } - $relayhost_details = relayhost('details', $relayhost_id); - if (!empty($relayhost_details)) { + if ($transport_type == 'transport-map') { + $transport_details = transport('details', $transport_id); + $nexthop = $transport_details['nexthop']; + } + elseif ($transport_type == 'sender-dependent') { + $transport_details = relayhost('details', $transport_id); + $nexthop = $transport_details['hostname']; + } + if (!empty($transport_details)) { // Remove [ and ] - $hostname_w_port = preg_replace('/\[|\]/', '', $relayhost_details['hostname']); + $hostname_w_port = preg_replace('/\[|\]/', '', $nexthop); + $skip_lookup_mx = strpos($nexthop, '['); // Explode to hostname and port list($hostname, $port) = explode(':', $hostname_w_port); + // Try to get MX if host is not [host] + if ($skip_lookup_mx === false) { + getmxrr($hostname, $mx_records, $mx_weight); + if (!empty($mx_records)) { + for ($i = 0; $i < count($mx_records); $i++) { + $mxs[$mx_records[$i]] = $mx_weight[$i]; + } + asort ($mxs); + $records = array_keys($mxs); + echo 'Using first matched primary MX for "' . $hostname . '": '; + $hostname = $records[0]; + echo $hostname . '<br>'; + } + else { + echo 'No MX records for ' . $hostname . ' were found in DNS, skipping and using hostname as next-hop.<br>'; + } + } // Use port 25 if no port was given $port = (empty($port)) ? 25 : $port; - $username = $relayhost_details['username']; - $password = $relayhost_details['password']; + $username = $transport_details['username']; + $password = $transport_details['password']; $mail = new PHPMailer; $mail->Timeout = 10; @@ -32,6 +58,9 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi ) ); $mail->SMTPDebug = 3; + if ($port == 465) { + $mail->SMTPSecure = "ssl"; + } $mail->Debugoutput = function($str, $level) { foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){ if (empty($line)) { continue; } @@ -73,7 +102,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi $mail->send(); } else { - echo "Unknown relayhost."; + echo "Unknown transport."; } } else { diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index cdef8e0b..f1cca54c 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -3,19 +3,17 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/footer.php'; logger(); ?> <div style="margin-bottom: 100px;"></div> -<script src="/js/bootstrap.min.js"></script> -<script src="/js/bootstrap-switch.min.js"></script> -<script src="/js/bootstrap-slider.min.js"></script> -<script src="/js/bootstrap-select.min.js"></script> -<script src="/js/bootstrap-filestyle.min.js"></script> -<script src="/js/notifications.min.js"></script> -<script src="/js/formcache.min.js"></script> -<script src="/js/google.charts.loader.js"></script> -<script src="/js/numberedtextarea.min.js"></script> -<script src="/js/u2f-api.js"></script> -<script src="/js/api.js"></script> +<script type='text/javascript'><?=$js_minifier->minify();?></script> <script> -var loading_text = '<?= $lang['footer']['loading']; ?>' +<?php +$lang_footer = json_encode($lang['footer']); +$lang_acl = json_encode($lang['acl']); +$lang_tfa = json_encode($lang['tfa']); +echo "var lang_footer = ". $lang_footer . ";\n"; +echo "var lang_acl = ". $lang_acl . ";\n"; +echo "var lang_tfa = ". $lang_tfa . ";\n"; +echo "var docker_timeout = ". $DOCKER_TIMEOUT * 1000 . ";\n"; +?> $(window).scroll(function() { sessionStorage.scrollTop = $(this).scrollTop(); }); @@ -28,17 +26,12 @@ $(window).load(function() { $(".overlay").hide(); }); $(document).ready(function() { - window.mailcow_alert_box = function(message, type) { - msg = $('<span/>').text(message).text(); - if (type == 'danger') { - auto_hide = 0; - $('#' + localStorage.getItem("add_modal")).modal('show'); - localStorage.removeItem("add_modal"); - } else { - auto_hide = 5000; - } - $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}}); - } + $(document).on('shown.bs.modal', function(e) { + modal_id = $(e.relatedTarget).data('target'); + $(modal_id).attr("aria-hidden","false"); + }); + // TFA, CSRF, Alerts in footer.inc.php + // Other general functions in mailcow.js <?php $alertbox_log_parser = alertbox_log_parser($_SESSION); if (is_array($alertbox_log_parser)) { @@ -50,7 +43,6 @@ $(document).ready(function() { unset($_SESSION['return']); } ?> - $('[data-cached-form="true"]').formcache({key: $(this).data('id')}); // Confirm TFA modal <?php if (isset($_SESSION['pending_tfa_method'])):?> $('#ConfirmTFAModal').modal({ @@ -59,7 +51,7 @@ $(document).ready(function() { }); $('#u2f_status_auth').html('<p><span class="glyphicon glyphicon-refresh glyphicon-spin"></span> Initializing, please wait...</p>'); $('#ConfirmTFAModal').on('shown.bs.modal', function(){ - $(this).find('#token').focus(); + $(this).find('input[name=token]').focus(); // If U2F if(document.getElementById("u2f_auth_data") !== null) { $.ajax({ @@ -68,7 +60,7 @@ $(document).ready(function() { dataType: 'script', url: "/api/v1/get/u2f-authentication/<?= (isset($_SESSION['pending_mailcow_cc_username'])) ? rawurlencode($_SESSION['pending_mailcow_cc_username']) : null; ?>", complete: function(data){ - $('#u2f_status_auth').html('<?=$lang['tfa']['waiting_usb_auth'];?>'); + $('#u2f_status_auth').html(lang_tfa.waiting_usb_auth); data; setTimeout(function() { console.log("Ready to authenticate"); @@ -98,7 +90,6 @@ $(document).ready(function() { <?php endif; ?> // Set TFA modals - $('#selectTFA').change(function () { if ($(this).val() == "yubi_otp") { $('#YubiOTPModal').modal('show'); @@ -106,6 +97,15 @@ $(document).ready(function() { } if ($(this).val() == "totp") { $('#TOTPModal').modal('show'); + request_token = $('#tfa-qr-img').data('totp-secret'); + $.ajax({ + url: '/inc/ajax/qr_gen.php', + data: { + token: request_token, + }, + }).done(function (result) { + $("#tfa-qr-img").attr("src", result); + }); $("option:selected").prop("selected", false); } if ($(this).val() == "u2f") { @@ -121,7 +121,7 @@ $(document).ready(function() { data; setTimeout(function() { console.log("Ready to register"); - $('#u2f_status_reg').html('<?=$lang['tfa']['waiting_usb_register'];?>'); + $('#u2f_status_reg').html(lang_tfa.waiting_usb_register); u2f.register(appId, registerRequests, registeredKeys, function(deviceResponse) { var form = document.getElementById('u2f_reg_form'); var reg = document.getElementById('u2f_register_data'); @@ -146,94 +146,8 @@ $(document).ready(function() { } }); - $(function () { - $('[data-toggle="tooltip"]').tooltip() - }); - - // Remember last navigation pill - (function () { - 'use strict'; - if ($('a[data-toggle="tab"]').length) { - $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { - if ($(this).data('dont-remember') == 1) { - return true; - } - var id = $(this).parents('[role="tablist"]').attr('id'); - var key = 'lastTag'; - if (id) { - key += ':' + id; - } - localStorage.setItem(key, $(e.target).attr('href')); - }); - $('[role="tablist"]').each(function (idx, elem) { - var id = $(elem).attr('id'); - var key = 'lastTag'; - if (id) { - key += ':' + id; - } - var lastTab = localStorage.getItem(key); - if (lastTab) { - $('[href="' + lastTab + '"]').tab('show'); - } - }); - } - })(); - - // Disable submit after submitting form (not API driven buttons) - $('form').submit(function() { - if ($('form button[type="submit"]').data('submitted') == '1') { - return false; - } else { - $(this).find('button[type="submit"]').first().text('<?= $lang['footer']['loading']; ?>'); - $('form button[type="submit"]').attr('data-submitted', '1'); - function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); }; - $(document).on("keydown", disableF5); - } - }); - - // IE fix to hide scrollbars when table body is empty - $('tbody').filter(function (index) { - return $(this).children().length < 1; - }).remove(); - - // Init Bootstrap Selectpicker - $('select').selectpicker(); - - // Trigger container restart - $('#RestartContainer').on('show.bs.modal', function(e) { - var container = $(e.relatedTarget).data('container'); - $('#containerName').text(container); - $('#triggerRestartContainer').click(function(){ - $(this).prop("disabled",true); - $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> '); - $('#statusTriggerRestartContainer').html('<?= $lang['footer']['restarting_container']; ?>'); - $.ajax({ - method: 'get', - url: '/inc/ajax/container_ctrl.php', - timeout: <?= $DOCKER_TIMEOUT * 1000; ?>, - data: { - 'service': container, - 'action': 'restart' - } - }) - .always( function (data, status) { - $('#statusTriggerRestartContainer').append(data); - var htmlResponse = $.parseHTML(data) - if ($(htmlResponse).find('span').hasClass('text-success')) { - $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-ok"></span> '); - setTimeout(function(){ - $('#RestartContainer').modal('toggle'); - window.location = window.location.href.split("#")[0]; - }, 1200); - } else { - $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-remove"></span> '); - } - }) - }); - }) - // CSRF - $('<input type="hidden" value="<?= $_SESSION['CSRF']['TOKEN']; ?>">').attr('id', 'csrf_token').attr('name', 'csrf_token').appendTo('form'); + $('<input type="hidden" value="<?= $_SESSION['CSRF']['TOKEN']; ?>">').attr('name', 'csrf_token').appendTo('form'); if (sessionStorage.scrollTop != "undefined") { $(window).scrollTop(sessionStorage.scrollTop); } diff --git a/data/web/inc/functions.acl.inc.php b/data/web/inc/functions.acl.inc.php new file mode 100644 index 00000000..b2fbd766 --- /dev/null +++ b/data/web/inc/functions.acl.inc.php @@ -0,0 +1,216 @@ +<?php +function acl($_action, $_scope = null, $_data = null) { + global $pdo; + global $lang; + $_data_log = $_data; + switch ($_action) { + case 'edit': + switch ($_scope) { + case 'user': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + // Cast to array for single selections + $acls = (array)$_data['user_acl']; + // Create associative array from index array + // All set items are given 1 as value + foreach ($acls as $acl_key => $acl_val) { + $acl_post[$acl_val] = 1; + } + // Users cannot change their own ACL + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username) + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + // Read all available acl options by calling acl(get) + // Set all available acl options we cannot find in the post data to 0, else 1 + $is_now = acl('get', 'user', $username); + if (!empty($is_now)) { + foreach ($is_now as $acl_now_name => $acl_now_val) { + $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'Cannot determine current ACL' + ); + continue; + } + foreach ($set_acls as $set_acl_key => $set_acl_val) { + $stmt = $pdo->prepare("UPDATE `user_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . " + WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('acl_saved', $username) + ); + } + break; + case 'domainadmin': + if ($_SESSION['mailcow_cc_role'] != 'admin') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + // Cast to array for single selections + $acls = (array)$_data['da_acl']; + // Create associative array from index array + // All set items are given 1 as value + foreach ($acls as $acl_key => $acl_val) { + $acl_post[$acl_val] = 1; + } + // Users cannot change their own ACL + if ($_SESSION['mailcow_cc_role'] != 'admin') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + // Read all available acl options by calling acl(get) + // Set all available acl options we cannot find in the post data to 0, else 1 + $is_now = acl('get', 'domainadmin', $username); + if (!empty($is_now)) { + foreach ($is_now as $acl_now_name => $acl_now_val) { + $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'Cannot determine current ACL' + ); + continue; + } + foreach ($set_acls as $set_acl_key => $set_acl_val) { + $stmt = $pdo->prepare("UPDATE `da_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . " + WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('acl_saved', $username) + ); + } + break; + } + break; + case 'get': + switch ($_scope) { + case 'user': + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($data)) { + unset($data['username']); + return $data; + } + else { + return false; + } + break; + case 'domainadmin': + if ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin') { + return false; + } + if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $_SESSION['mailcow_cc_username'] != $_data) { + return false; + } + $stmt = $pdo->prepare("SELECT * FROM `da_acl` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($data)) { + unset($data['username']); + return $data; + } + else { + return false; + } + break; + } + break; + case 'to_session': + if (!isset($_SESSION['mailcow_cc_role'])) { + return false; + } + unset($_SESSION['acl']); + $username = strtolower(trim($_SESSION['mailcow_cc_username'])); + // Admins get access to all modules + if ($_SESSION['mailcow_cc_role'] == 'admin' || + (isset($_SESSION["dual-login"]["role"]) && $_SESSION["dual-login"]["role"] == 'admin')) { + $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';"); + $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($acl_all)) { + $acl['acl'][$row['Field']] = 1; + } + $stmt = $pdo->query("SHOW COLUMNS FROM `da_acl` WHERE `Field` != 'username';"); + $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($acl_all)) { + $acl['acl'][$row['Field']] = 1; + } + } + elseif ($_SESSION['mailcow_cc_role'] == 'domainadmin' || + (isset($_SESSION["dual-login"]["role"]) && $_SESSION["dual-login"]["role"] == 'domainadmin')) { + // Read all exting user_acl modules and set to 1 + $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';"); + $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($acl_all)) { + $acl['acl'][$row['Field']] = 1; + } + // Read da_acl rules for current user, OVERWRITE overlapping modules + // This prevents access to a users sync jobs, when a domain admins was not given access to sync jobs + $stmt = $pdo->prepare("SELECT * FROM `da_acl` WHERE `username` = :username"); + $stmt->execute(array(':username' => (isset($_SESSION["dual-login"]["username"])) ? $_SESSION["dual-login"]["username"] : $username)); + $acl_user = $stmt->fetch(PDO::FETCH_ASSOC); + foreach ($acl_user as $acl_user_key => $acl_user_val) { + $acl['acl'][$acl_user_key] = $acl_user_val; + } + unset($acl['acl']['username']); + } + elseif ($_SESSION['mailcow_cc_role'] == 'user') { + $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $acl['acl'] = $stmt->fetch(PDO::FETCH_ASSOC); + unset($acl['acl']['username']); + } + if (!empty($acl)) { + $_SESSION = array_merge($_SESSION, $acl); + } + break; + } +} \ No newline at end of file diff --git a/data/web/inc/functions.address_rewriting.inc.php b/data/web/inc/functions.address_rewriting.inc.php index 36fc4946..df67d5b7 100644 --- a/data/web/inc/functions.address_rewriting.inc.php +++ b/data/web/inc/functions.address_rewriting.inc.php @@ -44,8 +44,8 @@ function bcc($_action, $_data = null, $attr = null) { ); return false; } - $domain = idn_to_ascii($local_dest); - $local_dest_sane = '@' . idn_to_ascii($local_dest); + $domain = idn_to_ascii($local_dest, 0, INTL_IDNA_VARIANT_UTS46); + $local_dest_sane = '@' . idn_to_ascii($local_dest, 0, INTL_IDNA_VARIANT_UTS46); } elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) { @@ -87,25 +87,15 @@ function bcc($_action, $_data = null, $attr = null) { ); return false; } - try { - $stmt = $pdo->prepare("INSERT INTO `bcc_maps` (`local_dest`, `bcc_dest`, `domain`, `active`, `type`) VALUES - (:local_dest, :bcc_dest, :domain, :active, :type)"); - $stmt->execute(array( - ':local_dest' => $local_dest_sane, - ':bcc_dest' => $bcc_dest, - ':domain' => $domain, - ':active' => $active, - ':type' => $type - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('mysql_error', $e) - ); - return false; - } + $stmt = $pdo->prepare("INSERT INTO `bcc_maps` (`local_dest`, `bcc_dest`, `domain`, `active`, `type`) VALUES + (:local_dest, :bcc_dest, :domain, :active, :type)"); + $stmt->execute(array( + ':local_dest' => $local_dest_sane, + ':bcc_dest' => $bcc_dest, + ':domain' => $domain, + ':active' => $active, + ':type' => $type + )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), @@ -155,37 +145,27 @@ function bcc($_action, $_data = null, $attr = null) { ); continue; } - try { - $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps` - WHERE `local_dest` = :local_dest AND `type` = :type"); - $stmt->execute(array(':local_dest' => $local_dest, ':type' => $type)); - $id_now = $stmt->fetch(PDO::FETCH_ASSOC)['id']; + $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps` + WHERE `local_dest` = :local_dest AND `type` = :type"); + $stmt->execute(array(':local_dest' => $local_dest, ':type' => $type)); + $id_now = $stmt->fetch(PDO::FETCH_ASSOC)['id']; - if (isset($id_now) && $id_now != $id) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('bcc_exists', htmlspecialchars($local_dest), $type) - ); - continue; - } - - $stmt = $pdo->prepare("UPDATE `bcc_maps` SET `bcc_dest` = :bcc_dest, `active` = :active, `type` = :type WHERE `id`= :id"); - $stmt->execute(array( - ':bcc_dest' => $bcc_dest, - ':active' => $active, - ':type' => $type, - ':id' => $id - )); - } - catch (PDOException $e) { + if (isset($id_now) && $id_now != $id) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('mysql_error', $e) + 'msg' => array('bcc_exists', htmlspecialchars($local_dest), $type) ); continue; } + + $stmt = $pdo->prepare("UPDATE `bcc_maps` SET `bcc_dest` = :bcc_dest, `active` = :active, `type` = :type WHERE `id`= :id"); + $stmt->execute(array( + ':bcc_dest' => $bcc_dest, + ':active' => $active, + ':type' => $type, + ':id' => $id + )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), @@ -233,34 +213,33 @@ function bcc($_action, $_data = null, $attr = null) { return $bccdata; break; case 'delete': + if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; + } $ids = (array)$_data['id']; foreach ($ids as $id) { if (!is_numeric($id)) { return false; } - try { - $stmt = $pdo->prepare("SELECT `domain` FROM `bcc_maps` WHERE id = :id"); - $stmt->execute(array(':id' => $id)); - $domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain']; - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - } - catch (PDOException $e) { + $stmt = $pdo->prepare("SELECT `domain` FROM `bcc_maps` WHERE id = :id"); + $stmt->execute(array(':id' => $id)); + $domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('mysql_error', $e) + 'msg' => 'access_denied' ); continue; } + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), @@ -286,7 +265,7 @@ function recipient_map($_action, $_data = null, $attr = null) { $new_dest = strtolower(trim($_data['recipient_map_new'])); $active = intval($_data['active']); if (is_valid_domain_name($old_dest)) { - $old_dest_sane = '@' . idn_to_ascii($old_dest); + $old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46); } elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { $old_dest_sane = $old_dest; @@ -309,33 +288,22 @@ function recipient_map($_action, $_data = null, $attr = null) { } $rmaps = recipient_map('get'); foreach ($rmaps as $rmap) { - $old_dests_existing[] = recipient_map('details', $rmap)['recipient_map_old']; - } - if (in_array($old_dest_sane, $old_dests_existing)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('recipient_map_entry_exists', htmlspecialchars($old_dest)) - ); - return false; - } - try { - $stmt = $pdo->prepare("INSERT INTO `recipient_maps` (`old_dest`, `new_dest`, `active`) VALUES - (:old_dest, :new_dest, :active)"); - $stmt->execute(array( - ':old_dest' => $old_dest_sane, - ':new_dest' => $new_dest, - ':active' => $active - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('mysql_error', $e) - ); - return false; + if (recipient_map('details', $rmap)['recipient_map_old'] == $old_dest_sane) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_exists', htmlspecialchars($old_dest_sane)) + ); + return false; + } } + $stmt = $pdo->prepare("INSERT INTO `recipient_maps` (`old_dest`, `new_dest`, `active`) VALUES + (:old_dest, :new_dest, :active)"); + $stmt->execute(array( + ':old_dest' => $old_dest_sane, + ':new_dest' => $new_dest, + ':active' => $active + )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), @@ -363,7 +331,7 @@ function recipient_map($_action, $_data = null, $attr = null) { continue; } if (is_valid_domain_name($old_dest)) { - $old_dest_sane = '@' . idn_to_ascii($old_dest); + $old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46); } elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { $old_dest_sane = $old_dest; @@ -376,7 +344,6 @@ function recipient_map($_action, $_data = null, $attr = null) { ); continue; } - $active = intval($_data['active']); if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -387,38 +354,27 @@ function recipient_map($_action, $_data = null, $attr = null) { } $rmaps = recipient_map('get'); foreach ($rmaps as $rmap) { - $old_dests_existing[] = recipient_map('details', $rmap)['recipient_map_old']; - } - if (in_array($old_dest_sane, $old_dests_existing) && - recipient_map('details', $id)['recipient_map_old'] != $old_dest_sane) { + if ($rmap == $id) { continue; } + if (recipient_map('details', $rmap)['recipient_map_old'] == $old_dest_sane) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), 'msg' => array('recipient_map_entry_exists', htmlspecialchars($old_dest_sane)) ); - continue; - } - try { - $stmt = $pdo->prepare("UPDATE `recipient_maps` SET - `old_dest` = :old_dest, - `new_dest` = :new_dest, - `active` = :active - WHERE `id`= :id"); - $stmt->execute(array( - ':old_dest' => $old_dest_sane, - ':new_dest' => $new_dest, - ':active' => $active, - ':id' => $id - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('mysql_error', $e) - ); - return false; + return false; + } } + $stmt = $pdo->prepare("UPDATE `recipient_maps` SET + `old_dest` = :old_dest, + `new_dest` = :new_dest, + `active` = :active + WHERE `id`= :id"); + $stmt->execute(array( + ':old_dest' => $old_dest_sane, + ':new_dest' => $new_dest, + ':active' => $active, + ':id' => $id + )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), @@ -463,24 +419,14 @@ function recipient_map($_action, $_data = null, $attr = null) { if (!is_numeric($id)) { return false; } - try { - $stmt = $pdo->prepare("DELETE FROM `recipient_maps` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('mysql_error', $e) - ); - return false; - } + $stmt = $pdo->prepare("DELETE FROM `recipient_maps` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_deleted', htmlspecialchars($id)) + ); } - $_SESSION['return'][] = array( - 'type' => 'success', - 'msg' => array('recipient_map_entry_deleted', htmlspecialchars($old_dest)) - ); - return true; break; } } diff --git a/data/web/inc/functions.admin.inc.php b/data/web/inc/functions.admin.inc.php new file mode 100644 index 00000000..f9aee508 --- /dev/null +++ b/data/web/inc/functions.admin.inc.php @@ -0,0 +1,276 @@ +<?php +function admin($_action, $_data = null) { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + global $pdo; + global $lang; + $_data_log = $_data; + !isset($_data_log['password']) ?: $_data_log['password'] = '*'; + !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; + switch ($_action) { + case 'add': + $username = strtolower(trim($_data['username'])); + $password = $_data['password']; + $password2 = $_data['password2']; + $active = intval($_data['active']); + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'username_invalid' + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `username` FROM `admin` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + foreach ($num_results as $num_results_each) { + if ($num_results_each != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_exists', htmlspecialchars($username)) + ); + return false; + } + } + if (!empty($password) && !empty($password2)) { + if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'password_complexity' + ); + return false; + } + if ($password != $password2) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'password_mismatch' + ); + return false; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) + VALUES (:username, :password_hashed, '1', :active)"); + $stmt->execute(array( + ':username' => $username, + ':password_hashed' => $password_hashed, + ':active' => $active + )); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'password_empty' + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('admin_added', htmlspecialchars($username)) + ); + break; + case 'edit': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + $is_now = admin('details', $username); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $password = $_data['password']; + $password2 = $_data['password2']; + if ($active == 0) { + $left_active = 0; + foreach (admin('get') as $admin) { + $left_active = $left_active + admin('details', $admin)['active_int']; + } + if ($left_active == 1) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'no_active_admin' + ); + continue; + } + } + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username_new) + ); + continue; + } + if ($username_new != $username) { + if (!empty(admin('details', $username_new)['username'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username_new) + ); + continue; + } + } + if (!empty($password) && !empty($password2)) { + if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'password_complexity' + ); + continue; + } + if ($password != $password2) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'password_mismatch' + ); + continue; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active + )); + if (isset($_data['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + } + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); + } + } + else { + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); + $stmt->execute(array( + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active + )); + if (isset($_data['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + } + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('admin_modified', htmlspecialchars($username)) + ); + } + return true; + break; + case 'delete': + $usernames = (array)$_data['username']; + foreach ($usernames as $username) { + if ($_SESSION['mailcow_cc_username'] == $username) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'cannot_delete_self' + ); + continue; + } + if (empty(admin('details', $username))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username) + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('admin_removed', htmlspecialchars($username)) + ); + } + break; + case 'get': + $admins = array(); + $stmt = $pdo->query("SELECT `username` FROM `admin` WHERE `superadmin` = '1'"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $admins[] = $row['username']; + } + return $admins; + break; + case 'details': + $admindata = array(); + $stmt = $pdo->prepare("SELECT + `tfa`.`active` AS `tfa_active_int`, + CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`, + `admin`.`username`, + `admin`.`created`, + `admin`.`active` AS `active_int`, + CASE `admin`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + FROM `admin` + LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`admin`.`username` + WHERE `admin`.`username`= :admin AND `superadmin` = '1'"); + $stmt->execute(array( + ':admin' => $_data + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + return false; + } + $admindata['username'] = $row['username']; + $admindata['tfa_active'] = $row['tfa_active']; + $admindata['active'] = $row['active']; + $admindata['tfa_active_int'] = $row['tfa_active_int']; + $admindata['active_int'] = $row['active_int']; + $admindata['created'] = $row['created']; + return $admindata; + break; + } +} diff --git a/data/web/inc/functions.dkim.inc.php b/data/web/inc/functions.dkim.inc.php index 0338289b..f4bfd997 100644 --- a/data/web/inc/functions.dkim.inc.php +++ b/data/web/inc/functions.dkim.inc.php @@ -123,7 +123,7 @@ function dkim($_action, $_data = null) { try { $redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']); $redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']); - $redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, trim($from_domain_dkim['privkey'])); + $redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey']))); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -227,7 +227,7 @@ function dkim($_action, $_data = null) { return true; break; case 'details': - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) && $_SESSION['mailcow_cc_role'] != "admin") { return false; } $dkimdata = array(); @@ -307,4 +307,4 @@ function dkim($_action, $_data = null) { } break; } -} \ No newline at end of file +} diff --git a/data/web/inc/functions.docker.inc.php b/data/web/inc/functions.docker.inc.php index dbd26375..54d4b139 100644 --- a/data/web/inc/functions.docker.inc.php +++ b/data/web/inc/functions.docker.inc.php @@ -2,10 +2,13 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $extra_headers = null) { global $DOCKER_TIMEOUT; $curl = curl_init(); - curl_setopt($curl, CURLOPT_HTTPHEADER,array( 'Content-Type: application/json' )); + curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: application/json' )); + // We are using our mail certificates for dockerapi, the names will not match, the certs are trusted anyway + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); switch($action) { case 'get_id': - curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/json'); + curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 0); curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT); @@ -13,24 +16,16 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex if ($response === false) { $err = curl_error($curl); curl_close($curl); - // logger(array('return' => array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => $err, - // ))); return $err; } else { curl_close($curl); - // logger(array('return' => array( - // 'type' => 'success', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // ))); $containers = json_decode($response, true); if (!empty($containers)) { foreach ($containers as $container) { - if ($container['Config']['Labels']['com.docker.compose.service'] == $service_name - && $container['Config']['Labels']['com.docker.compose.project'] == getenv('COMPOSE_PROJECT_NAME')) { + if (isset($container['Config']['Labels']['com.docker.compose.service']) + && $container['Config']['Labels']['com.docker.compose.service'] == $service_name + && $container['Config']['Labels']['com.docker.compose.project'] == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { return trim($container['Id']); } } @@ -38,7 +33,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex } return false; case 'containers': - curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/json'); + curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 0); curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT); @@ -46,23 +41,14 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex if ($response === false) { $err = curl_error($curl); curl_close($curl); - // logger(array('return' => array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => $err, - // ))); return $err; } else { curl_close($curl); - // logger(array('return' => array( - // 'type' => 'success', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // ))); $containers = json_decode($response, true); if (!empty($containers)) { foreach ($containers as $container) { - if ($container['Config']['Labels']['com.docker.compose.project'] == getenv('COMPOSE_PROJECT_NAME')) { + if ($container['Config']['Labels']['com.docker.compose.project'] == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { $out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State']; $out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config']; } @@ -74,7 +60,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex break; case 'info': if (empty($service_name)) { - curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/json'); + curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 0); curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT); @@ -82,14 +68,9 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex else { $container_id = docker('get_id', $service_name); if (ctype_xdigit($container_id)) { - curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/json'); + curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/' . $container_id . '/json'); } else { - // logger(array('return' => array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => 'invalid_container_id' - // ))); return false; } } @@ -100,24 +81,16 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex if ($response === false) { $err = curl_error($curl); curl_close($curl); - // logger(array('return' => array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => $err, - // ))); return $err; } else { curl_close($curl); - // logger(array('return' => array( - // 'type' => 'success', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // ))); $decoded_response = json_decode($response, true); if (!empty($decoded_response)) { if (empty($service_name)) { foreach ($decoded_response as $container) { - if ($container['Config']['Labels']['com.docker.compose.project'] == getenv('COMPOSE_PROJECT_NAME')) { + if (isset($container['Config']['Labels']['com.docker.compose.project']) + && $container['Config']['Labels']['com.docker.compose.project'] == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { unset($container['Config']['Env']); $out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State']; $out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config']; @@ -125,7 +98,8 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex } } else { - if ($decoded_response['Config']['Labels']['com.docker.compose.project'] == getenv('COMPOSE_PROJECT_NAME')) { + if (isset($decoded_response['Config']['Labels']['com.docker.compose.project']) + && $decoded_response['Config']['Labels']['com.docker.compose.project'] == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { unset($container['Config']['Env']); $out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['State'] = $decoded_response['State']; $out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['Config'] = $decoded_response['Config']; @@ -144,7 +118,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex if (!empty($attr1)) { $container_id = docker('get_id', $service_name); if (ctype_xdigit($container_id) && ctype_alnum($attr1)) { - curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/' . $attr1); + curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/' . $container_id . '/' . $attr1); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT); if (!empty($attr2)) { @@ -158,19 +132,10 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex if ($response === false) { $err = curl_error($curl); curl_close($curl); - logger(array('return' => array(array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - 'msg' => $err, - )))); return $err; } else { curl_close($curl); - logger(array('return' => array(array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - )))); if (empty($response)) { return true; } diff --git a/data/web/inc/functions.domain_admin.inc.php b/data/web/inc/functions.domain_admin.inc.php index 86cec621..064ba6c8 100644 --- a/data/web/inc/functions.domain_admin.inc.php +++ b/data/web/inc/functions.domain_admin.inc.php @@ -1,11 +1,13 @@ <?php - function domain_admin($_action, $_data = null) { global $pdo; global $lang; $_data_log = $_data; !isset($_data_log['password']) ?: $_data_log['password'] = '*'; !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; + !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*'; + !isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*'; + !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*'; switch ($_action) { case 'add': $username = strtolower(trim($_data['username'])); @@ -90,43 +92,22 @@ function domain_admin($_action, $_data = null) { ); return false; } - try { - $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) - VALUES (:username, :domain, :created, :active)"); - $stmt->execute(array( - ':username' => $username, - ':domain' => $domain, - ':created' => date('Y-m-d H:i:s'), - ':active' => $active - )); - } - catch (PDOException $e) { - domain_admin('delete', $username); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - } - try { - $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) - VALUES (:username, :password_hashed, '0', :active)"); + $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) + VALUES (:username, :domain, :created, :active)"); $stmt->execute(array( ':username' => $username, - ':password_hashed' => $password_hashed, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), ':active' => $active )); } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } + $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) + VALUES (:username, :password_hashed, '0', :active)"); + $stmt->execute(array( + ':username' => $username, + ':password_hashed' => $password_hashed, + ':active' => $active + )); } else { $_SESSION['return'][] = array( @@ -136,6 +117,10 @@ function domain_admin($_action, $_data = null) { ); return false; } + $stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)"); + $stmt->execute(array( + ':username' => $username + )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data_log), @@ -179,15 +164,14 @@ function domain_admin($_action, $_data = null) { $password = $_data['password']; $password2 = $_data['password2']; if (!empty($domains)) { - foreach ($domains as $i => &$domain) { + foreach ($domains as $domain) { if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data_log), 'msg' => array('domain_invalid', htmlspecialchars($domain)) ); - unset($domains[$i]); - continue; + continue 2; } } } @@ -209,44 +193,27 @@ function domain_admin($_action, $_data = null) { continue; } } - try { - $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array( + ':username_new' => $username_new, + ':username' => $username + )); if (!empty($domains)) { foreach ($domains as $domain) { - try { - $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) - VALUES (:username_new, :domain, :created, :active)"); - $stmt->execute(array( - ':username_new' => $username_new, - ':domain' => $domain, - ':created' => date('Y-m-d H:i:s'), - ':active' => $active - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } + $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) + VALUES (:username_new, :domain, :created, :active)"); + $stmt->execute(array( + ':username_new' => $username_new, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':active' => $active + )); } } - if (!empty($password) && !empty($password2)) { if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { $_SESSION['return'][] = array( @@ -265,56 +232,36 @@ function domain_admin($_action, $_data = null) { continue; } $password_hashed = hash_password($password); - try { - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username_new' => $username_new, - ':username' => $username, - ':active' => $active - )); - if (isset($_data['disable_tfa'])) { - $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - } - else { - $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); - $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); - } + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active + )); + if (isset($_data['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); } } else { - try { - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); - $stmt->execute(array( - ':username_new' => $username_new, - ':username' => $username, - ':active' => $active - )); - if (isset($_data['disable_tfa'])) { - $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - } - else { - $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); - $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); - } + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); + $stmt->execute(array( + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active + )); + if (isset($_data['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); } } $_SESSION['return'][] = array( @@ -337,7 +284,6 @@ function domain_admin($_action, $_data = null) { WHERE `username` = :user"); $stmt->execute(array(':user' => $username)); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!verify_hash($row['password'], $password_old)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -346,7 +292,6 @@ function domain_admin($_action, $_data = null) { ); return false; } - if (!empty($password_new2) && !empty($password_new)) { if ($password_new2 != $password_new) { $_SESSION['return'][] = array( @@ -365,21 +310,11 @@ function domain_admin($_action, $_data = null) { return false; } $password_hashed = hash_password($password_new); - try { - $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username' => $username - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } + $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username + )); } $_SESSION['return'][] = array( 'type' => 'success', @@ -399,7 +334,7 @@ function domain_admin($_action, $_data = null) { } $usernames = (array)$_data['username']; foreach ($usernames as $username) { - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + if (empty(domain_admin('details', $username))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data_log), @@ -407,24 +342,18 @@ function domain_admin($_action, $_data = null) { ); continue; } - try { - $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data_log), @@ -442,7 +371,6 @@ function domain_admin($_action, $_data = null) { ); return false; } - $stmt = $pdo->query("SELECT DISTINCT `username` FROM `domain_admins` @@ -454,23 +382,19 @@ function domain_admin($_action, $_data = null) { while ($row = array_shift($rows)) { $domainadmins[] = $row['username']; } - return $domainadmins; break; case 'details': $domainadmindata = array(); - if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) { return false; } elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { return false; } - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) { return false; } - $stmt = $pdo->prepare("SELECT `tfa`.`active` AS `tfa_active_int`, CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`, @@ -493,7 +417,7 @@ function domain_admin($_action, $_data = null) { $domainadmindata['active'] = $row['active']; $domainadmindata['tfa_active_int'] = $row['tfa_active_int']; $domainadmindata['active_int'] = $row['active_int']; - $domainadmindata['modified'] = $row['created']; + $domainadmindata['created'] = $row['created']; // GET SELECTED $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain` IN ( diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php index 166606ef..1cde10d3 100644 --- a/data/web/inc/functions.fail2ban.inc.php +++ b/data/web/inc/functions.fail2ban.inc.php @@ -113,10 +113,28 @@ function fail2ban($_action, $_data = null) { $redis->hDel('F2B_BLACKLIST', $network, 1); $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $network) + ); + continue; + } } elseif ($_data['action'] == "blacklist") { if (valid_network($network)) { $redis->hSet('F2B_BLACKLIST', $network, 1); + $redis->hDel('F2B_WHITELIST', $network, 1); + $response = docker('post', 'netfilter-mailcow', 'restart'); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $network) + ); + continue; } } } diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index a44cf7eb..ebce5819 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1,4 +1,12 @@ <?php +function isset_has_content($var) { + if (isset($var) && $var != "") { + return true; + } + else { + return false; + } +} function hash_password($password) { $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str); @@ -18,6 +26,109 @@ function last_login($user) { return false; } } +function flush_memcached() { + try { + $m = new Memcached(); + $m->addServer('memcached', 11211); + $m->flush(); + } + catch ( Exception $e ) { + // Dunno + } +} +function sys_mail($_data) { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'access_denied' + ); + return false; + } + $excludes = $_data['mass_exclude']; + $includes = $_data['mass_include']; + $mailboxes = array(); + $mass_from = $_data['mass_from']; + $mass_text = $_data['mass_text']; + $mass_subject = $_data['mass_subject']; + if (!filter_var($mass_from, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'from_invalid' + ); + return false; + } + if (empty($mass_subject)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'subject_empty' + ); + return false; + } + if (empty($mass_text)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'text_empty' + ); + return false; + } + $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); + foreach ($domains as $domain) { + foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) { + $mailboxes[] = $mailbox; + } + } + if (!empty($includes)) { + $rcpts = array_intersect($mailboxes, $includes); + } + elseif (!empty($excludes)) { + $rcpts = array_diff($mailboxes, $excludes); + } + else { + $rcpts = $mailboxes; + } + if (!empty($rcpts)) { + ini_set('max_execution_time', 0); + ini_set('max_input_time', 0); + $mail = new PHPMailer; + $mail->Timeout = 10; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + $mail->isSMTP(); + $mail->Host = 'dovecot-mailcow'; + $mail->SMTPAuth = false; + $mail->Port = 24; + $mail->setFrom($mass_from); + $mail->Subject = $mass_subject; + $mail->CharSet ="UTF-8"; + $mail->Body = $mass_text; + $mail->XMailer = 'MooMassMail'; + foreach ($rcpts as $rcpt) { + $mail->AddAddress($rcpt); + if (!$mail->send()) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__), + 'msg' => 'Mailer error (RCPT "' . htmlspecialchars($rcpt) . '"): ' . str_replace('https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting', '', $mail->ErrorInfo) + ); + } + $mail->ClearAllRecipients(); + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__), + 'msg' => 'Mass mail job completed, sent ' . count($rcpts) . ' mails' + ); +} function logger($_data = false) { /* logger() will be called as last function @@ -64,17 +175,23 @@ function logger($_data = false) { $user = 'unauthenticated'; $role = 'unauthenticated'; } - $stmt = $pdo->prepare("INSERT INTO `logs` (`type`, `task`, `msg`, `call`, `user`, `role`, `remote`, `time`) VALUES - (:type, :task, :msg, :call, :user, :role, :remote, UNIX_TIMESTAMP())"); - $stmt->execute(array( - ':type' => $type, - ':task' => $task, - ':call' => $call, - ':msg' => $msg, - ':user' => $user, - ':role' => $role, - ':remote' => get_remote_ip() - )); + // We cannot log when logs is missing... + try { + $stmt = $pdo->prepare("INSERT INTO `logs` (`type`, `task`, `msg`, `call`, `user`, `role`, `remote`, `time`) VALUES + (:type, :task, :msg, :call, :user, :role, :remote, UNIX_TIMESTAMP())"); + $stmt->execute(array( + ':type' => $type, + ':task' => $task, + ':call' => $call, + ':msg' => $msg, + ':user' => $user, + ':role' => $role, + ':remote' => get_remote_ip() + )); + } + catch (Exception $e) { + // Do nothing + } } } else { @@ -89,21 +206,35 @@ function hasDomainAccess($username, $role, $domain) { if (empty($domain) || !is_valid_domain_name($domain)) { return false; } - if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { + if ($role != 'admin' && $role != 'domainadmin') { return false; } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain_admins` - WHERE ( - `active`='1' - AND `username` = :username - AND (`domain` = :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)) - ) - OR 'admin' = :role"); - $stmt->execute(array(':username' => $username, ':domain1' => $domain, ':domain2' => $domain, ':role' => $role)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if (!empty($num_results)) { - return true; - } + if ($role == 'admin') { + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` + WHERE `alias_domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + return true; + } + } + elseif ($role == 'domainadmin') { + $stmt = $pdo->prepare("SELECT `domain` FROM `domain_admins` + WHERE ( + `active`='1' + AND `username` = :username + AND (`domain` = :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)) + )"); + $stmt->execute(array(':username' => $username, ':domain1' => $domain, ':domain2' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if (!empty($num_results)) { + return true; + } + } return false; } function hasMailboxObjectAccess($username, $role, $object) { @@ -260,11 +391,18 @@ function verify_hash($hash, $password) { return true; } } + elseif (preg_match('/^{MD5-CRYPT}/i', $hash)) { + $hash = preg_replace('/^{MD5-CRYPT}/i', '', $hash); + if (password_verify($password, $hash)) { + return true; + } + } return false; } function check_login($user, $pass) { global $pdo; global $redis; + global $imap_server; if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -276,6 +414,7 @@ function check_login($user, $pass) { $user = strtolower(trim($user)); $stmt = $pdo->prepare("SELECT `password` FROM `admin` WHERE `superadmin` = '1' + AND `active` = '1' AND `username` = :user"); $stmt->execute(array(':user' => $user)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -295,6 +434,9 @@ function check_login($user, $pass) { } else { unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $user, '*'), @@ -373,54 +515,6 @@ function check_login($user, $pass) { sleep($_SESSION['ldelay']); return false; } -function set_acl() { - global $pdo; - if (!isset($_SESSION['mailcow_cc_username'])) { - return false; - } - if ($_SESSION['mailcow_cc_role'] == 'admin' || $_SESSION['mailcow_cc_role'] == 'domainadmin') { - $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';"); - $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($acl_all)) { - $acl['acl'][$row['Field']] = 1; - } - } - else { - $username = strtolower(trim($_SESSION['mailcow_cc_username'])); - $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $acl['acl'] = $stmt->fetch(PDO::FETCH_ASSOC); - unset($acl['acl']['username']); - } - if (!empty($acl)) { - $_SESSION = array_merge($_SESSION, $acl); - } - else { - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__), - 'msg' => 'set_acl_failed' - ); - return false; - } -} -function get_acl($username) { - global $pdo; - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $username = strtolower(trim($username)); - $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $acl = $stmt->fetch(PDO::FETCH_ASSOC); - unset($acl['username']); - if (!empty($acl)) { - return $acl; - } - else { - return false; - } -} function formatBytes($size, $precision = 2) { if(!is_numeric($size)) { return "0"; @@ -432,110 +526,6 @@ function formatBytes($size, $precision = 2) { } return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; } -function edit_admin_account($_data) { - global $lang; - global $pdo; - $_data_log = $_data; - !isset($_data_log['admin_pass']) ?: $_data_log['admin_pass'] = '*'; - !isset($_data_log['admin_pass2']) ?: $_data_log['admin_pass2'] = '*'; - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $username_now = $_SESSION['mailcow_cc_username']; - $username = $_data['admin_user']; - $password = $_data['admin_pass']; - $password2 = $_data['admin_pass2']; - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'username_invalid' - ); - return false; - } - if (!empty($password) && !empty($password2)) { - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'password_complexity' - ); - return false; - } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'password_mismatch' - ); - return false; - } - $password_hashed = hash_password($password); - try { - $stmt = $pdo->prepare("UPDATE `admin` SET - `password` = :password_hashed, - `username` = :username1 - WHERE `username` = :username2"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username1' => $username, - ':username2' => $username_now - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - } - else { - try { - $stmt = $pdo->prepare("UPDATE `admin` SET - `username` = :username1 - WHERE `username` = :username2"); - $stmt->execute(array( - ':username1' => $username, - ':username2' => $username_now - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - } - try { - $stmt = $pdo->prepare("UPDATE `domain_admins` SET `domain` = 'ALL', `username` = :username1 WHERE `username` = :username2"); - $stmt->execute(array(':username1' => $username, ':username2' => $username_now)); - $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username1 WHERE `username` = :username2"); - $stmt->execute(array(':username1' => $username, ':username2' => $username_now)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['mailcow_cc_username'] = $username; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'admin_modified' - ); -} function update_sogo_static_view() { global $pdo; global $lang; @@ -543,9 +533,11 @@ function update_sogo_static_view() { WHERE TABLE_NAME = 'sogo_view'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); if ($num_results != 0) { - $stmt = $pdo->query("REPLACE INTO _sogo_static_view SELECT * from sogo_view"); + $stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `kind`, `multiple_bookings`) + SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `kind`, `multiple_bookings` from sogo_view"); $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); } + flush_memcached(); } function edit_user_account($_data) { global $lang; @@ -628,6 +620,8 @@ function edit_user_account($_data) { function user_get_alias_details($username) { global $lang; global $pdo; + $data['direct_aliases'] = false; + $data['shared_aliases'] = false; if ($_SESSION['mailcow_cc_role'] == "user") { $username = $_SESSION['mailcow_cc_username']; } @@ -635,7 +629,7 @@ function user_get_alias_details($username) { return false; } $data['address'] = $username; - $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') AS `shared_aliases` FROM `alias` + $stmt = $pdo->prepare("SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias` WHERE `goto` REGEXP :username_goto AND `address` NOT LIKE '@%' AND `goto` != :username_goto2 @@ -647,9 +641,12 @@ function user_get_alias_details($username) { )); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['shared_aliases'] = $row['shared_aliases']; + $data['shared_aliases'][$row['shared_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); + + //$data['shared_aliases'][] = $row['shared_aliases']; } - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`address` SEPARATOR ', ') AS `direct_aliases` FROM `alias` + + $stmt = $pdo->prepare("SELECT `address` AS `direct_aliases`, `public_comment` FROM `alias` WHERE `goto` = :username_goto AND `address` NOT LIKE '@%' AND `address` != :username_address"); @@ -657,21 +654,22 @@ function user_get_alias_details($username) { array( ':username_goto' => $username, ':username_address' => $username - )); + )); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['direct_aliases'][] = $row['direct_aliases']; + $data['direct_aliases'][$row['direct_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); } - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', ') AS `ad_alias` FROM `mailbox` + $stmt = $pdo->prepare("SELECT CONCAT(local_part, '@', alias_domain) AS `ad_alias`, `alias_domain` FROM `mailbox` LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain` WHERE `username` = :username ;"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['direct_aliases'][] = $row['ad_alias']; + if (empty($row['ad_alias'])) { + continue; + } + $data['direct_aliases'][$row['ad_alias']]['public_comment'] = 'โช ' . $row['alias_domain']; } - $data['direct_aliases'] = implode(', ', array_filter($data['direct_aliases'])); - $data['direct_aliases'] = empty($data['direct_aliases']) ? '✘' : $data['direct_aliases']; $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -696,7 +694,7 @@ function is_valid_domain_name($domain_name) { if (empty($domain_name)) { return false; } - $domain_name = idn_to_ascii($domain_name); + $domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46); return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) && preg_match("/^.{1,253}$/", $domain_name) && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); @@ -1137,6 +1135,11 @@ function admin_api($action, $data = null) { $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from'])); foreach ($allow_from as $key => $val) { if (!filter_var($val, FILTER_VALIDATE_IP)) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $data), + 'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key])) + ); unset($allow_from[$key]); continue; } @@ -1157,16 +1160,24 @@ function admin_api($action, $data = null) { strtoupper(bin2hex(random_bytes(3))), strtoupper(bin2hex(random_bytes(3))) )); - $stmt = $pdo->prepare("INSERT INTO `api` (`username`, `api_key`, `active`, `allow_from`) - SELECT `username`, :api_key, :active, :allow_from FROM `admin` WHERE `superadmin`='1' AND `active`='1' - ON DUPLICATE KEY UPDATE `active` = :active_u, `allow_from` = :allow_from_u ;"); - $stmt->execute(array( - ':api_key' => $api_key, - ':active' => $active, - ':active_u' => $active, - ':allow_from' => $allow_from, - ':allow_from_u' => $allow_from - )); + $stmt = $pdo->query("SELECT `api_key` FROM `api`"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if (empty($num_results)) { + $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `active`, `allow_from`) + VALUES (:api_key, :active, :allow_from);"); + $stmt->execute(array( + ':api_key' => $api_key, + ':active' => $active, + ':allow_from' => $allow_from + )); + } + else { + $stmt = $pdo->prepare("UPDATE `api` SET `active` = :active, `allow_from` = :allow_from ;"); + $stmt->execute(array( + ':active' => $active, + ':allow_from' => $allow_from + )); + } break; case "regen_key": $api_key = implode('-', array( @@ -1176,17 +1187,21 @@ function admin_api($action, $data = null) { strtoupper(bin2hex(random_bytes(3))), strtoupper(bin2hex(random_bytes(3))) )); - $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `username` IN - (SELECT `username` FROM `admin` WHERE `superadmin`='1' AND `active`='1')"); + $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key"); $stmt->execute(array( ':api_key' => $api_key )); break; + case "get": + $stmt = $pdo->query("SELECT * FROM `api`"); + $apidata = $stmt->fetch(PDO::FETCH_ASSOC); + return $apidata; + break; } $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $data), - 'msg' => 'admin_modified' + 'msg' => 'admin_api_modified' ); } function rspamd_ui($action, $data = null) { @@ -1227,7 +1242,7 @@ function rspamd_ui($action, $data = null) { ); return false; } - $docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json')); + $docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'rspamd', 'task' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json')); if ($docker_return_array = json_decode($docker_return, true)) { if ($docker_return_array['type'] == 'success') { $_SESSION['return'][] = array( @@ -1257,28 +1272,13 @@ function rspamd_ui($action, $data = null) { break; } } -function get_admin_details() { - // No parameter to be given, only one admin should exist - global $pdo; - global $lang; - $data = array(); - if ($_SESSION['mailcow_cc_role'] != 'admin') { - return false; - } - $stmt = $pdo->query("SELECT `admin`.`username`, `api`.`active` AS `api_active`, `api`.`api_key`, `api`.`allow_from` FROM `admin` - INNER JOIN `api` ON `admin`.`username` = `api`.`username` - WHERE `admin`.`superadmin`='1' - AND `admin`.`active`='1'"); - $data = $stmt->fetch(PDO::FETCH_ASSOC); - return $data; -} function get_u2f_registrations($username) { global $pdo; $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'"); $sel->execute(array($username)); return $sel->fetchAll(PDO::FETCH_OBJ); } -function get_logs($container, $lines = false) { +function get_logs($application, $lines = false) { if ($lines === false) { $lines = $GLOBALS['LOG_LINES'] - 1; } @@ -1298,7 +1298,7 @@ function get_logs($container, $lines = false) { return false; } // SQL - if ($container == "mailcow-ui") { + if ($application == "mailcow-ui") { if (isset($from) && isset($to)) { $stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :from, :to"); $stmt->execute(array( @@ -1319,7 +1319,7 @@ function get_logs($container, $lines = false) { } } // Redis - if ($container == "dovecot-mailcow") { + if ($application == "dovecot-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1); } @@ -1333,7 +1333,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "postfix-mailcow") { + if ($application == "postfix-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1); } @@ -1347,7 +1347,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "sogo-mailcow") { + if ($application == "sogo-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1); } @@ -1361,7 +1361,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "watchdog-mailcow") { + if ($application == "watchdog-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1); } @@ -1375,7 +1375,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "acme-mailcow") { + if ($application == "acme-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('ACME_LOG', $from - 1, $to - 1); } @@ -1389,7 +1389,21 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "api-mailcow") { + if ($application == "ratelimited") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('RL_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('RL_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "api-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('API_LOG', $from - 1, $to - 1); } @@ -1403,7 +1417,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "netfilter-mailcow") { + if ($application == "netfilter-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1); } @@ -1417,7 +1431,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "autodiscover-mailcow") { + if ($application == "autodiscover-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1); } @@ -1431,9 +1445,9 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "rspamd-history") { + if ($application == "rspamd-history") { $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/rspamd-sock/rspamd.sock'); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); if (!is_numeric($lines)) { list ($from, $to) = explode('-', $lines); curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?from=" . intval($from) . "&to=" . intval($to)); @@ -1453,4 +1467,43 @@ function get_logs($container, $lines = false) { } return false; } +function getGUID() { + if (function_exists('com_create_guid')) { + return com_create_guid(); + } + mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up. + $charid = strtoupper(md5(uniqid(rand(), true))); + $hyphen = chr(45);// "-" + return substr($charid, 0, 8).$hyphen + .substr($charid, 8, 4).$hyphen + .substr($charid,12, 4).$hyphen + .substr($charid,16, 4).$hyphen + .substr($charid,20,12); +} +function solr_status() { + $curl = curl_init(); + $endpoint = 'http://solr:8983/solr/admin/cores'; + $params = array( + 'action' => 'STATUS', + 'core' => 'dovecot-fts', + 'indexInfo' => 'true' + ); + $url = $endpoint . '?' . http_build_query($params); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 0); + curl_setopt($curl, CURLOPT_TIMEOUT, 20); + $response = curl_exec($curl); + if ($response === false) { + $err = curl_error($curl); + curl_close($curl); + return false; + } + else { + curl_close($curl); + $status = json_decode($response, true); + return (!empty($status['status']['dovecot-fts'])) ? $status['status']['dovecot-fts'] : false; + } + return false; +} ?> diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index dd40fa0e..88b8e91e 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -3,6 +3,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { global $pdo; global $redis; global $lang; + global $MAILBOX_DEFAULT_ATTRIBUTES; $_data_log = $_data; !isset($_data_log['password']) ?: $_data_log['password'] = '*'; !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; @@ -321,7 +322,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $domain = idn_to_ascii(strtolower(trim($_data['domain']))); + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $description = $_data['description']; $aliases = $_data['aliases']; $mailboxes = $_data['mailboxes']; @@ -347,6 +348,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $active = intval($_data['active']); $relay_all_recipients = intval($_data['relay_all_recipients']); $backupmx = intval($_data['backupmx']); + $gal = intval($_data['gal']); ($relay_all_recipients == 1) ? $backupmx = '1' : null; if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( @@ -390,8 +392,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `backupmx`, `active`, `relay_all_recipients`) - VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, :backupmx, :active, :relay_all_recipients)"); + $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_all_recipients`) + VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, :backupmx, :gal, :active, :relay_all_recipients)"); $stmt->execute(array( ':domain' => $domain, ':description' => $description, @@ -400,6 +402,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':maxquota' => $maxquota, ':quota' => $quota, ':backupmx' => $backupmx, + ':gal' => $gal, ':active' => $active, ':relay_all_recipients' => $relay_all_recipients )); @@ -450,6 +453,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $goto_null = intval($_data['goto_null']); $goto_spam = intval($_data['goto_spam']); $goto_ham = intval($_data['goto_ham']); + $private_comment = $_data['private_comment']; + $public_comment = $_data['public_comment']; + if (strlen($private_comment) > 160 | strlen($public_comment) > 160){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'comment_too_long' + ); + return false; + } if (empty($addresses[0])) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -480,7 +493,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (empty($goto)) { continue; } - $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1)); + $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); $goto_local_part = strstr($goto, '@', true); $goto = $goto_local_part.'@'.$goto_domain; $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` @@ -518,7 +531,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (in_array($address, $gotos)) { continue; } - $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); + $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); $local_part = strstr($address, '@', true); $address = $local_part.'@'.$domain; $domaindata = mailbox('get', 'domain_details', $domain); @@ -548,7 +561,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) ); - return false; + continue; } $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); @@ -560,7 +573,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('domain_not_found', htmlspecialchars($domain)) ); - return false; + continue; } $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :address"); @@ -572,7 +585,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('is_spam_alias', htmlspecialchars($address)) ); - return false; + continue; } if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { $_SESSION['return'][] = array( @@ -580,7 +593,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => 'alias_invalid' ); - return false; + continue; } if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { $_SESSION['return'][] = array( @@ -588,12 +601,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => 'access_denied' ); - return false; + continue; } - $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`) - VALUES (:address, :goto, :domain, :active)"); + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `active`) + VALUES (:address, :public_comment, :private_comment, :goto, :domain, :active)"); if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) { $stmt->execute(array( + ':address' => '@'.$domain, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, ':address' => '@'.$domain, ':goto' => $goto, ':domain' => $domain, @@ -603,6 +619,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $stmt->execute(array( ':address' => $address, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, ':goto' => $goto, ':domain' => $domain, ':active' => $active @@ -619,7 +637,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $active = intval($_data['active']); $alias_domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['alias_domain'])); $alias_domains = array_filter($alias_domains); - $target_domain = idn_to_ascii(strtolower(trim($_data['target_domain']))); + $target_domain = idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46); + if (!isset($_SESSION['acl']['alias_domains']) || $_SESSION['acl']['alias_domains'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } if (!is_valid_domain_name($target_domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -637,7 +663,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($alias_domains as $i => $alias_domain) { - $alias_domain = idn_to_ascii(strtolower(trim($alias_domain))); + $alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46); if (!is_valid_domain_name($alias_domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -697,6 +723,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } + if (!empty(intval($_data['rl_value']))) { + ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $alias_domain)); + } $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -706,7 +735,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { break; case 'mailbox': $local_part = strtolower(trim($_data['local_part'])); - $domain = idn_to_ascii(strtolower(trim($_data['domain']))); + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $username = $local_part . '@' . $domain; if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { $_SESSION['return'][] = array( @@ -726,14 +755,31 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $password = $_data['password']; $password2 = $_data['password2']; - $name = $_data['name']; - $quota_m = filter_var($_data['quota'], FILTER_SANITIZE_NUMBER_FLOAT); + $name = ltrim(rtrim($_data['name'], '>'), '<'); + $quota_m = intval($_data['quota']); + if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['quarantine_notification'] != "1") && $quota_m === 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'unlimited_quota_acl' + ); + return false; + } if (empty($name)) { $name = $local_part; } $active = intval($_data['active']); $quota_b = ($quota_m * 1048576); - $maildir = $domain . "/" . $local_part . "/"; + $mailbox_attrs = json_encode( + array( + 'force_pw_update' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'])), + 'tls_enforce_in' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'])), + 'tls_enforce_out' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'])), + 'sogo_access' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'])), + 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']), + 'quarantine_notification' => strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']) + ) + ); if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -806,14 +852,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!is_numeric($quota_m) || $quota_m == "0") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'quota_not_0_not_numeric' - ); - return false; - } if (!empty($password) && !empty($password2)) { if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { $_SESSION['return'][] = array( @@ -866,16 +904,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `attributes`, `active`) - VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, '{\"force_pw_update\": \"0\", \"tls_enforce_in\": \"0\", \"tls_enforce_out\": \"0\"}', :active)"); + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`) + VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)"); $stmt->execute(array( ':username' => $username, ':password_hashed' => $password_hashed, ':name' => $name, - ':maildir' => $maildir, ':quota_b' => $quota_b, ':local_part' => $local_part, ':domain' => $domain, + ':mailbox_attrs' => $mailbox_attrs, ':active' => $active )); $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`) @@ -900,7 +938,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); break; case 'resource': - $domain = idn_to_ascii(strtolower(trim($_data['domain']))); + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $description = $_data['description']; $local_part = preg_replace('/[^\da-z]/i', '', preg_quote($description, '/')); $name = $local_part . '@' . $domain; @@ -994,8 +1032,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`) - VALUES (:name, 'RESOURCE', :description, 'RESOURCE', 0, :local_part, :domain, :active, :multiple_bookings, :kind)"); + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`) + VALUES (:name, 'RESOURCE', :description, 0, :local_part, :domain, :active, :multiple_bookings, :kind)"); $stmt->execute(array( ':name' => $name, ':description' => $description, @@ -1018,11 +1056,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { case 'alias_domain': $alias_domains = (array)$_data['alias_domain']; foreach ($alias_domains as $alias_domain) { - $alias_domain = idn_to_ascii(strtolower(trim($alias_domain))); + $alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46); $is_now = mailbox('get', 'alias_domain_details', $alias_domain); if (!empty($is_now)) { $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; - $target_domain = (!empty($_data['target_domain'])) ? idn_to_ascii(strtolower(trim($_data['target_domain']))) : $is_now['target_domain']; + $target_domain = (!empty($_data['target_domain'])) ? idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46) : $is_now['target_domain']; } else { $_SESSION['return'][] = array( @@ -1126,6 +1164,65 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; + case 'quarantine_notification': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $is_now = mailbox('get', 'quarantine_notification', $username); + if (!empty($is_now)) { + $quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!in_array($quarantine_notification, array('never', 'hourly', 'daily', 'weekly'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `mailbox` + SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', :quarantine_notification) + WHERE `username` = :username"); + $stmt->execute(array( + ':quarantine_notification' => $quarantine_notification, + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; case 'spam_score': if (!is_array($_data['username'])) { $usernames = array(); @@ -1143,13 +1240,26 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($usernames as $username) { + if ($_data['spam_score'] == "default") { + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username + AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + continue; + } $lowspamlevel = explode(',', $_data['spam_score'])[0]; $highspamlevel = explode(',', $_data['spam_score'])[1]; if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' + 'msg' => 'Invalid spam score, format must be "1,2" where first is low and second is high spam value.' ); continue; } @@ -1205,12 +1315,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = (`validity` + 3600) WHERE - `address` = :address AND - `validity` >= :validity"); + if (empty($_data['validity'])) { + continue; + } + $validity = round((int)time() + ($_data['validity'] * 3600)); + $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE + `address` = :address"); $stmt->execute(array( ':address' => $address, - ':validity' => time() + ':validity' => $validity )); $_SESSION['return'][] = array( 'type' => 'success', @@ -1452,14 +1565,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } break; case 'filter': - $sieve = new Sieve\SieveParser(); - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1468,6 +1573,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } + $sieve = new Sieve\SieveParser(); + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } foreach ($ids as $id) { $is_now = mailbox('get', 'filter_details', $id); if (!empty($is_now)) { @@ -1549,6 +1662,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0; $goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0; $goto_ham = (isset($_data['goto_ham'])) ? intval($_data['goto_ham']) : 0; + $public_comment = (isset($_data['public_comment'])) ? $_data['public_comment'] : $is_now['public_comment']; + $private_comment = (isset($_data['private_comment'])) ? $_data['private_comment'] : $is_now['private_comment']; $goto = (!empty($_data['goto'])) ? $_data['goto'] : $is_now['goto']; $address = (!empty($_data['address'])) ? $_data['address'] : $is_now['address']; } @@ -1561,7 +1676,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { continue; } if ($is_now['address'] != $address) { - $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); + $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); $local_part = strstr($address, '@', true); $address = $local_part.'@'.$domain; if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { @@ -1665,11 +1780,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($goto)) { $stmt = $pdo->prepare("UPDATE `alias` SET `address` = :address, + `public_comment` = :public_comment, + `private_comment` = :private_comment, `goto` = :goto, `active`= :active WHERE `id` = :id"); $stmt->execute(array( ':address' => $address, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, ':goto' => $goto, ':active' => $active, ':id' => $id @@ -1691,7 +1810,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $domains = $_data['domain']; } foreach ($domains as $domain) { - $domain = idn_to_ascii($domain); + $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1702,13 +1821,26 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } if ($_SESSION['mailcow_cc_role'] == "domainadmin" && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $description = $_data['description']; - $active = intval($_data['active']); + $is_now = mailbox('get', 'domain_details', $domain); + if (!empty($is_now)) { + $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal_int']; + $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } $stmt = $pdo->prepare("UPDATE `domain` SET - `description` = :description + `description` = :description, + `gal` = :gal WHERE `domain` = :domain"); $stmt->execute(array( ':description' => $description, + ':gal' => $gal, ':domain' => $domain )); $_SESSION['return'][] = array( @@ -1722,10 +1854,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($is_now)) { $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx_int']; + $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal_int']; $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients_int']; $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost']; $aliases = (!empty($_data['aliases'])) ? $_data['aliases'] : $is_now['max_num_aliases_for_domain']; - $mailboxes = (!empty($_data['mailboxes'])) ? $_data['mailboxes'] : $is_now['max_num_mboxes_for_domain']; + $mailboxes = (isset($_data['mailboxes']) && $_data['mailboxes'] != '') ? intval($_data['mailboxes']) : $is_now['max_num_mboxes_for_domain']; $maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576); $quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576); $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; @@ -1808,6 +1941,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt = $pdo->prepare("UPDATE `domain` SET `relay_all_recipients` = :relay_all_recipients, `backupmx` = :backupmx, + `gal` = :gal, `active` = :active, `quota` = :quota, `maxquota` = :maxquota, @@ -1819,6 +1953,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':relay_all_recipients' => $relay_all_recipients, ':backupmx' => $backupmx, + ':gal' => $gal, ':active' => $active, ':quota' => $quota, ':maxquota' => $maxquota, @@ -1857,9 +1992,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($is_now)) { $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); - $name = (!empty($_data['name'])) ? $_data['name'] : $is_now['name']; + (int)$sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); + (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); + $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; $domain = $is_now['domain']; - $quota_m = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['quota'] / 1048576); $quota_b = $quota_m * 1048576; $password = (!empty($_data['password'])) ? $_data['password'] : null; $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; @@ -1872,6 +2008,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + // if already 0 == ok + if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'unlimited_quota_acl' + ); + return false; + } $stmt = $pdo->prepare("SELECT `quota`, `maxquota` FROM `domain` WHERE `domain` = :domain"); @@ -1885,14 +2030,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - if (!is_numeric($quota_m) || $quota_m == "0") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('quota_not_0_not_numeric', htmlspecialchars($quota_m)) - ); - continue; - } if ($quota_m > $DomainData['maxquota']) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -2058,13 +2195,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `active` = :active, `name`= :name, `quota` = :quota_b, - `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update) + `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update), + `attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access) WHERE `username` = :username"); $stmt->execute(array( ':active' => $active, ':name' => $name, ':quota_b' => $quota_b, ':force_pw_update' => $force_pw_update, + ':sogo_access' => $sogo_access, ':username' => $username )); $_SESSION['return'][] = array( @@ -2269,7 +2408,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` != 'ALL' AND `domain` = :domain"); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain"); $stmt->execute(array( ':domain' => $_data, )); @@ -2310,6 +2449,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'tls_enforce_out' => $attrs['tls_enforce_out'] ); break; + case 'quarantine_notification': + $attrs = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $attrs = $stmt->fetch(PDO::FETCH_ASSOC); + $attrs = json_decode($attrs['attributes'], true); + return $attrs['quarantine_notification']; + break; case 'filters': $filters = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { @@ -2360,20 +2515,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_data = $_SESSION['mailcow_cc_username']; } $exec_fields = array( - 'cmd' => 'sieve_list', + 'cmd' => 'sieve', + 'task' => 'list', 'username' => $_data ); - $filters = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); - $filters = array_filter(explode(PHP_EOL, $filters)); + $filters = docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + $filters = array_filter(preg_split("/(\r\n|\n|\r)/",$filters)); foreach ($filters as $filter) { if (preg_match('/.+ ACTIVE/i', $filter)) { $exec_fields = array( - 'cmd' => 'sieve_print', + 'cmd' => 'sieve', + 'task' => 'print', 'script_name' => substr($filter, 0, -7), 'username' => $_data ); - $filters = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); - return preg_replace('/^.+\n/', '', $filters); + $script = docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + // Remove first line + return preg_replace('/^.+\n/', '', $script); } } return false; @@ -2444,7 +2602,47 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return $syncjobdata; break; case 'spam_score': - $default = "5, 15"; + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/actions"); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $default_actions = curl_exec($curl); + if (!curl_errno($curl)) { + $data_array = json_decode($default_actions, true); + curl_close($curl); + foreach ($data_array as $data) { + if ($data['action'] == 'reject') { + $reject = $data['value']; + continue; + } + elseif ($data['action'] == 'add header') { + $add_header = $data['value']; + continue; + } + } + if (empty($add_header) || empty($reject)) { + // Assume default, set warning + $default = "5, 15"; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not determine servers default spam score, assuming default' + ); + } + else { + $default = $add_header . ', ' . $reject; + } + } + else { + // Assume default, set warning + $default = "5, 15"; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not determine servers default spam score, assuming default' + ); + } + curl_close($curl); $policydata = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { @@ -2527,7 +2725,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` != 'ALL' AND `domain` = :domain"); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` = :domain"); $stmt->execute(array( ':domain' => $_data, )); @@ -2599,6 +2797,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `domain`, `goto`, `address`, + `public_comment`, + `private_comment`, `active` as `active_int`, CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, `created`, @@ -2622,6 +2822,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $aliasdata['id'] = $row['id']; $aliasdata['domain'] = $row['domain']; + $aliasdata['public_comment'] = $row['public_comment']; + $aliasdata['private_comment'] = $row['private_comment']; + $aliasdata['domain'] = $row['domain']; $aliasdata['goto'] = $row['goto']; $aliasdata['address'] = $row['address']; (!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0; @@ -2672,8 +2875,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { SELECT `domain` from `domain_admins` WHERE (`active`='1' AND `username` = :username)) ) - OR ('admin'= :role) - AND `domain` != 'ALL'"); + OR 'admin'= :role"); $stmt->execute(array( ':username' => $_SESSION['mailcow_cc_username'], ':role' => $_SESSION['mailcow_cc_role'], @@ -2686,7 +2888,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { break; case 'domain_details': $domaindata = array(); - $_data = idn_to_ascii(strtolower(trim($_data))); + $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } @@ -2708,9 +2910,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `relayhost`, `relay_all_recipients` as `relay_all_recipients_int`, `backupmx` as `backupmx_int`, + `gal` as `gal_int`, `active` as `active_int`, CASE `relay_all_recipients` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `relay_all_recipients`, CASE `backupmx` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `backupmx`, + CASE `gal` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `gal`, CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` FROM `domain` WHERE `domain`= :domain"); $stmt->execute(array( @@ -2743,7 +2947,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576; $domaindata['relayhost'] = $row['relayhost']; $domaindata['backupmx'] = $row['backupmx']; + $domaindata['gal'] = $row['gal']; $domaindata['backupmx_int'] = $row['backupmx_int']; + $domaindata['gal_int'] = $row['gal_int']; $domaindata['rl'] = $rl; $domaindata['active'] = $row['active']; $domaindata['active_int'] = $row['active_int']; @@ -2776,6 +2982,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `mailbox`.`active` AS `active_int`, CASE `mailbox`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, `mailbox`.`domain`, + `mailbox`.`local_part`, `mailbox`.`quota`, `quota2`.`bytes`, `attributes`, @@ -2806,13 +3013,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $mailboxdata['active'] = $row['active']; $mailboxdata['active_int'] = $row['active_int']; $mailboxdata['domain'] = $row['domain']; + $mailboxdata['local_part'] = $row['local_part']; $mailboxdata['quota'] = $row['quota']; $mailboxdata['attributes'] = json_decode($row['attributes'], true); $mailboxdata['quota_used'] = intval($row['bytes']); - $mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100); + $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100); $mailboxdata['messages'] = $row['messages']; $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count']; - if ($mailboxdata['percent_in_use'] >= 90) { + if ($mailboxdata['percent_in_use'] === '- ') { + $mailboxdata['percent_class'] = "info"; + } + elseif ($mailboxdata['percent_in_use'] >= 90) { $mailboxdata['percent_class'] = "danger"; } elseif ($mailboxdata['percent_in_use'] >= 75) { @@ -3016,6 +3227,66 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; + case 'sogo_profile': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['sogo_profile_reset']) || $_SESSION['acl']['sogo_profile_reset'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sogo_profile_reset', htmlspecialchars($username)) + ); + } + break; case 'domain': if (!is_array($_data['domain'])) { $domains = array(); @@ -3041,7 +3312,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $domain = idn_to_ascii(strtolower(trim($domain))); + $domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46); $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `domain` = :domain"); $stmt->execute(array(':domain' => $domain)); @@ -3054,6 +3325,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $domain); + $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); + if ($maildir_gc['type'] != 'success') { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not move mail storage to garbage collector: ' . $maildir_gc['msg'] + ); + } $stmt = $pdo->prepare("DELETE FROM `domain` WHERE `domain` = :domain"); $stmt->execute(array( ':domain' => $domain, @@ -3078,17 +3358,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':domain' => '%@'.$domain, )); - $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :domain"); + $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` LIKE :domain"); $stmt->execute(array( ':domain' => '%@'.$domain, )); - $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` = :domain"); + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` LIKE :domain"); $stmt->execute(array( ':domain' => '%@'.$domain, )); $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :domain"); $stmt->execute(array( - ':domain' => '%@'.$domain, + ':domain' => $domain, )); $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :domain"); $stmt->execute(array( @@ -3227,10 +3507,53 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + $mailbox_details = mailbox('get', 'mailbox_details', $username); + if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) { + $maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part']; + $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir); + $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); + if ($maildir_gc['type'] != 'success') { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] + ); + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty' + ); + } + if (strtolower(getenv('SKIP_SOLR')) == 'n') { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true'); + curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml')); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, '<delete><query>user:' . $username . '</query></delete>'); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + $response = curl_exec($curl); + if ($response === false) { + $err = curl_error($curl); + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not remove Solr index: ' . print_r($err, true) + ); + } + curl_close($curl); + } $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :username"); $stmt->execute(array( ':username' => $username )); + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `rcpt` = :username"); + $stmt->execute(array( + ':username' => $username + )); $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :username"); $stmt->execute(array( ':username' => $username @@ -3243,6 +3566,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':username' => $username )); + // fk, better safe than sorry + $stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username"); $stmt->execute(array( ':username' => $username @@ -3390,7 +3718,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } break; } - if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox'))) { + if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource'))) { update_sogo_static_view(); } } diff --git a/data/web/inc/functions.mailq.inc.php b/data/web/inc/functions.mailq.inc.php new file mode 100644 index 00000000..24de47be --- /dev/null +++ b/data/web/inc/functions.mailq.inc.php @@ -0,0 +1,104 @@ +<?php +function mailq($_action, $_data = null) { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + function process_mailq_output($returned_output, $_action, $_data) { + if ($returned_output !== NULL) { + if (isset($returned_output['type']) && $returned_output['type'] == 'danger') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array('mailq', $_action, $_data), + 'msg' => 'Error: ' . $returned_output['msg'] + ); + } + if (isset($returned_output['type']) && $returned_output['type'] == 'success') { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array('mailq', $_action, $_data), + 'msg' => 'queue_command_success' + ); + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array('mailq', $_action, $_data), + 'msg' => 'unknown' + ); + } + } + global $lang; + switch ($_action) { + case 'get': + $mailq_lines = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'list')); + $lines = 0; + // Hard limit to 10000 items + foreach (preg_split("/((\r?\n)|(\r\n?))/", $mailq_lines) as $mailq_item) if ($lines++ < 10000) { + if (empty($mailq_item) || $mailq_item == '1') { + continue; + } + $mq_line = json_decode($mailq_item, true); + if ($mq_line !== NULL) { + $rcpts = array(); + foreach ($mq_line['recipients'] as $rcpt) { + if (isset($rcpt['delay_reason'])) { + $rcpts[] = $rcpt['address'] . ' (' . $rcpt['delay_reason'] . ')'; + } + else { + $rcpts[] = $rcpt['address']; + } + } + if (!empty($rcpts)) { + $mq_line['recipients'] = $rcpts; + } + $line[] = $mq_line; + } + } + if (!isset($line) || empty($line)) { + return '{}'; + } + else { + return json_encode($line); + } + break; + case 'delete': + if (!is_array($_data['qid'])) { + $qids = array(); + $qids[] = $_data['qid']; + } + else { + $qids = $_data['qid']; + } + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'delete', 'items' => $qids)); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + break; + case 'edit': + if (in_array($_data['action'], array('hold', 'unhold', 'deliver'))) { + if (!is_array($_data['qid'])) { + $qids = array(); + $qids[] = $_data['qid']; + } + else { + $qids = $_data['qid']; + } + if (!empty($qids)) { + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'], 'items' => $qids)); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + } + } + if (in_array($_data['action'], array('flush', 'super_delete'))) { + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'])); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + } + break; + case 'get': + // todo: move get from json_api here + break; + } +} diff --git a/data/web/inc/functions.policy.inc.php b/data/web/inc/functions.policy.inc.php index c8526169..ad29bd24 100644 --- a/data/web/inc/functions.policy.inc.php +++ b/data/web/inc/functions.policy.inc.php @@ -6,6 +6,14 @@ function policy($_action, $_scope, $_data = null) { $_data_log = $_data; switch ($_action) { case 'add': + if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } switch ($_scope) { case 'domain': $object = $_data['domain']; @@ -18,7 +26,7 @@ function policy($_action, $_scope, $_data = null) { ); return false; } - $object = idn_to_ascii(strtolower(trim($object))); + $object = idn_to_ascii(strtolower(trim($object)), 0, INTL_IDNA_VARIANT_UTS46); } else { $_SESSION['return'][] = array( @@ -34,7 +42,7 @@ function policy($_action, $_scope, $_data = null) { elseif ($_data['object_list'] == "wl") { $object_list = "whitelist_from"; } - $object_from = preg_replace('/\.+/', '.', rtrim(preg_replace("/\.\*/", "*", trim(strtolower($_data['object_from']))), '.')); + $object_from = trim(strtolower($_data['object_from'])); if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -90,21 +98,13 @@ function policy($_action, $_scope, $_data = null) { ); return false; } - if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } if ($_data['object_list'] == "bl") { $object_list = "blacklist_from"; } elseif ($_data['object_list'] == "wl") { $object_list = "whitelist_from"; } - $object_from = preg_replace('/\.+/', '.', rtrim(preg_replace("/\.\*/", "*", trim(strtolower($_data['object_from']))), '.')); + $object_from = trim(strtolower($_data['object_from'])); if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -151,6 +151,14 @@ function policy($_action, $_scope, $_data = null) { } break; case 'delete': + if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } switch ($_scope) { case 'domain': (array)$prefids = $_data['prefid']; @@ -175,7 +183,7 @@ function policy($_action, $_scope, $_data = null) { ); continue; } - $object = idn_to_ascii(strtolower(trim($object))); + $object = idn_to_ascii(strtolower(trim($object)), 0, INTL_IDNA_VARIANT_UTS46); } else { $_SESSION['return'][] = array( @@ -215,14 +223,6 @@ function policy($_action, $_scope, $_data = null) { else { $prefids = $_data['prefid']; } - if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } foreach ($prefids as $prefid) { if (!is_numeric($prefid)) { $_SESSION['return'][] = array( @@ -277,7 +277,7 @@ function policy($_action, $_scope, $_data = null) { if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } - $_data = idn_to_ascii(strtolower(trim($_data))); + $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); } // WHITELIST diff --git a/data/web/inc/functions.quarantine.inc.php b/data/web/inc/functions.quarantine.inc.php index c92a2cdd..34dbf39a 100644 --- a/data/web/inc/functions.quarantine.inc.php +++ b/data/web/inc/functions.quarantine.inc.php @@ -5,6 +5,221 @@ function quarantine($_action, $_data = null) { global $lang; $_data_log = $_data; switch ($_action) { + case 'quick_delete': + // Dont return results, just log + $hash = trim($_data); + if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return; + } + $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` + WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash + AND user_acl.quarantine = 1 + AND rcpt IN (SELECT username FROM mailbox)'); + $stmt->execute(array(':hash' => $hash)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row['id']) || !is_numeric($row['id'])) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return; + } + else { + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id"); + $stmt->execute(array( + ':id' => $row['id'] + )); + } + logger(array('return' => array( + array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_deleted', $row['id']) + ) + ))); + break; + case 'quick_release': + // Dont return results, just log + $hash = trim($_data); + if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return; + } + $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` + WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash + AND `user_acl`.`quarantine` = 1 + AND `username` IN (SELECT `username` FROM `mailbox`)'); + $stmt->execute(array(':hash' => $hash)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row['id']) || !is_numeric($row['id'])) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return; + } + else { + $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id'); + $stmt->execute(array(':id' => $row['id'])); + $detail_row = $stmt->fetch(PDO::FETCH_ASSOC); + $sender = (isset($detail_row['sender'])) ? $detail_row['sender'] : 'sender-unknown@rspamd'; + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ) + ))); + return; + } + try { + $release_format = $redis->Get('Q_RELEASE_FORMAT'); + } + catch (RedisException $e) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ) + ))); + return; + } + if ($release_format == 'attachment') { + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->SMTPDebug = 0; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ) + ))); + return; + } + $mail->Host = $postfix; + $mail->Port = 590; + $mail->setFrom($sender); + $mail->CharSet = 'UTF-8'; + $mail->Subject = sprintf($lang['quarantine']['release_subject'], $detail_row['qid']); + $mail->addAddress($detail_row['rcpt']); + $mail->IsHTML(false); + $msg_tmpf = tempnam("/tmp", $detail_row['qid']); + file_put_contents($msg_tmpf, $detail_row['msg']); + $mail->addAttachment($msg_tmpf, $detail_row['qid'] . '.eml'); + $mail->Body = sprintf($lang['quarantine']['release_body']); + $mail->send(); + unlink($msg_tmpf); + } + catch (phpmailerException $e) { + unlink($msg_tmpf); + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', $e->errorMessage()) + ) + ))); + return; + } + } + elseif ($release_format == 'raw') { + $postfix_talk = array( + array('220', 'HELO quarantine' . chr(10)), + array('250', 'MAIL FROM: ' . $sender . chr(10)), + array('250', 'RCPT TO: ' . $detail_row['rcpt'] . chr(10)), + array('250', 'DATA' . chr(10)), + array('354', $detail_row['msg'] . chr(10) . '.' . chr(10)), + array('250', 'QUIT' . chr(10)), + array('221', '') + ); + // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php + $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); + if (!$smtp_connection) { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Cannot connect to Postfix' + ) + ))); + return false; + } + for ($i=0; $i < count($postfix_talk); $i++) { + $smtp_resource = fgets($smtp_connection, 256); + if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { + $ret = substr($smtp_resource, 0, 3); + $ret = (empty($ret)) ? '-' : $ret; + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0] + ) + ))); + return; + } + if ($postfix_talk[$i][1] !== '') { + fputs($smtp_connection, $postfix_talk[$i][1]); + } + } + fclose($smtp_connection); + } + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id"); + $stmt->execute(array( + ':id' => $row['id'] + )); + } + logger(array('return' => array( + array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_released', $hash) + ) + ))); + break; case 'delete': if (!is_array($_data['id'])) { $ids = array(); @@ -74,12 +289,25 @@ function quarantine($_action, $_data = null) { return false; } $retention_size = $_data['retention_size']; + if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') { + $release_format = $_data['release_format']; + } + else { + $release_format = 'raw'; + } $max_size = $_data['max_size']; + $subject = $_data['subject']; + $sender = $_data['sender']; + $html = $_data['html_tmpl']; $exclude_domains = (array)$_data['exclude_domains']; try { $redis->Set('Q_RETENTION_SIZE', intval($retention_size)); $redis->Set('Q_MAX_SIZE', intval($max_size)); $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains)); + $redis->Set('Q_RELEASE_FORMAT', $release_format); + $redis->Set('Q_SENDER', $sender); + $redis->Set('Q_SUBJ', $subject); + $redis->Set('Q_HTML', $html); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -124,54 +352,119 @@ function quarantine($_action, $_data = null) { continue; } $sender = (isset($row['sender'])) ? $row['sender'] : 'sender-unknown@rspamd'; - try { - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->SMTPDebug = 0; - $mail->SMTPOptions = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true - ) - ); - if (!empty(gethostbynamel('postfix-mailcow'))) { - $postfix = 'postfix-mailcow'; - } - if (!empty(gethostbynamel('postfix'))) { - $postfix = 'postfix'; - } - else { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', 'Cannot determine Postfix host') - ); - continue; - } - $mail->Host = $postfix; - $mail->Port = 590; - $mail->setFrom($sender); - $mail->CharSet = 'UTF-8'; - $mail->Subject = sprintf($lang['quarantine']['release_subject'], $row['qid']); - $mail->addAddress($row['rcpt']); - $mail->IsHTML(false); - $msg_tmpf = tempnam("/tmp", $row['qid']); - file_put_contents($msg_tmpf, $row['msg']); - $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml'); - $mail->Body = sprintf($lang['quarantine']['release_body']); - $mail->send(); - unlink($msg_tmpf); + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; } - catch (phpmailerException $e) { - unlink($msg_tmpf); + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { $_SESSION['return'][] = array( 'type' => 'warning', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', $e->errorMessage()) + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') ); continue; } + try { + $release_format = $redis->Get('Q_RELEASE_FORMAT'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + if ($release_format == 'attachment') { + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->SMTPDebug = 0; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ); + continue; + } + $mail->Host = $postfix; + $mail->Port = 590; + $mail->setFrom($sender); + $mail->CharSet = 'UTF-8'; + $mail->Subject = sprintf($lang['quarantine']['release_subject'], $row['qid']); + $mail->addAddress($row['rcpt']); + $mail->IsHTML(false); + $msg_tmpf = tempnam("/tmp", $row['qid']); + file_put_contents($msg_tmpf, $row['msg']); + $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml'); + $mail->Body = sprintf($lang['quarantine']['release_body']); + $mail->send(); + unlink($msg_tmpf); + } + catch (phpmailerException $e) { + unlink($msg_tmpf); + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', $e->errorMessage()) + ); + continue; + } + } + elseif ($release_format == 'raw') { + $postfix_talk = array( + array('220', 'HELO quarantine' . chr(10)), + array('250', 'MAIL FROM: ' . $sender . chr(10)), + array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)), + array('250', 'DATA' . chr(10)), + array('354', $row['msg'] . chr(10) . '.' . chr(10)), + array('250', 'QUIT' . chr(10)), + array('221', '') + ); + // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php + $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); + if (!$smtp_connection) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Cannot connect to Postfix' + ); + return false; + } + for ($i=0; $i < count($postfix_talk); $i++) { + $smtp_resource = fgets($smtp_connection, 256); + if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { + $ret = substr($smtp_resource, 0, 3); + $ret = (empty($ret)) ? '-' : $ret; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0] + ); + return false; + } + if ($postfix_talk[$i][1] !== '') { + fputs($smtp_connection, $postfix_talk[$i][1]); + } + } + fclose($smtp_connection); + } try { $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); $stmt->execute(array( @@ -221,13 +514,13 @@ function quarantine($_action, $_data = null) { continue; } $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/rspamd-sock/rspamd.sock'); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain')); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain')); curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnspam"); - curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); $response = curl_exec($curl); if (!curl_errno($curl)) { $response = json_decode($response, true); @@ -243,23 +536,22 @@ function quarantine($_action, $_data = null) { } curl_close($curl); $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/rspamd-sock/rspamd.sock'); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd"); - curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); $response = curl_exec($curl); if (!curl_errno($curl)) { $response = json_decode($response, true); if (isset($response['error'])) { $_SESSION['return'][] = array( - 'type' => 'danger', + 'type' => 'warning', 'log' => array(__FUNCTION__), 'msg' => array('fuzzy_learn_error', $response['error']) ); - continue; } curl_close($curl); try { @@ -279,7 +571,7 @@ function quarantine($_action, $_data = null) { $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__), - 'msg' => 'qlearn_spam' + 'msg' => array('qlearn_spam', $id) ); continue; } @@ -288,7 +580,7 @@ function quarantine($_action, $_data = null) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => array('spam_learn_error', 'curl error ' . curl_errno($curl)) + 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) ); continue; } @@ -301,12 +593,12 @@ function quarantine($_action, $_data = null) { continue; } else { - curl_close($curl); $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => array('spam_learn_error', 'curl error ' . curl_errno($curl)) + 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) ); + curl_close($curl); continue; } curl_close($curl); @@ -322,7 +614,7 @@ function quarantine($_action, $_data = null) { break; case 'get': if ($_SESSION['mailcow_cc_role'] == "user") { - $stmt = $pdo->prepare('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` = :mbox'); + $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` = :mbox'); $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -330,7 +622,7 @@ function quarantine($_action, $_data = null) { } } elseif ($_SESSION['mailcow_cc_role'] == "admin") { - $stmt = $pdo->query('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine`'); + $stmt = $pdo->query('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine`'); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { $q_meta[] = $row; @@ -339,7 +631,7 @@ function quarantine($_action, $_data = null) { else { $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); foreach ($domains as $domain) { - $stmt = $pdo->prepare('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` REGEXP :domain'); + $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` REGEXP :domain'); $stmt->execute(array(':domain' => '@' . $domain . '$')); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -350,18 +642,19 @@ function quarantine($_action, $_data = null) { return $q_meta; break; case 'settings': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } try { - $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true); + if ($_SESSION['mailcow_cc_role'] == "admin") { + $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true); + } $settings['max_size'] = $redis->Get('Q_MAX_SIZE'); $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE'); + $settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT'); + $settings['subject'] = $redis->Get('Q_SUBJ'); + $settings['sender'] = $redis->Get('Q_SENDER'); + $settings['html_tmpl'] = htmlspecialchars($redis->Get('Q_HTML')); + if (empty($settings['html_tmpl'])) { + $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl")); + } } catch (RedisException $e) { $_SESSION['return'][] = array( diff --git a/data/web/inc/functions.quota_notification.inc.php b/data/web/inc/functions.quota_notification.inc.php new file mode 100644 index 00000000..61d101dc --- /dev/null +++ b/data/web/inc/functions.quota_notification.inc.php @@ -0,0 +1,65 @@ +<?php +function quota_notification($_action, $_data = null) { + global $redis; + global $lang; + $_data_log = $_data; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_action) { + case 'edit': + $retention_size = $_data['retention_size']; + if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') { + $release_format = $_data['release_format']; + } + else { + $release_format = 'raw'; + } + $subject = $_data['subject']; + $sender = $_data['sender']; + $html = $_data['html_tmpl']; + try { + $redis->Set('QW_SENDER', $sender); + $redis->Set('QW_SUBJ', $subject); + $redis->Set('QW_HTML', $html); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'saved_settings' + ); + break; + case 'get': + try { + $settings['subject'] = $redis->Get('QW_SUBJ'); + $settings['sender'] = $redis->Get('QW_SENDER'); + $settings['html_tmpl'] = htmlspecialchars($redis->Get('QW_HTML')); + if (empty($settings['html_tmpl'])) { + $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quota.tpl")); + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $settings; + break; + } +} diff --git a/data/web/inc/functions.ratelimit.inc.php b/data/web/inc/functions.ratelimit.inc.php index 2ead02e4..efde5ec5 100644 --- a/data/web/inc/functions.ratelimit.inc.php +++ b/data/web/inc/functions.ratelimit.inc.php @@ -5,6 +5,14 @@ function ratelimit($_action, $_scope, $_data = null) { $_data_log = $_data; switch ($_action) { case 'edit': + if (!isset($_SESSION['acl']['ratelimit']) || $_SESSION['acl']['ratelimit'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } switch ($_scope) { case 'domain': if (!is_array($_data['object'])) { @@ -184,5 +192,44 @@ function ratelimit($_action, $_scope, $_data = null) { break; } break; + case 'delete': + $data['hash'] = $_data; + if ($_SESSION['mailcow_cc_role'] != 'admin' || !preg_match('/^RL[0-9A-Za-z=]+$/i', trim($data['hash']))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + try { + if ($redis->exists($data['hash'])) { + $redis->delete($data['hash']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'hash_deleted' + ); + return true; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'hash_not_found' + ); + return false; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; } } \ No newline at end of file diff --git a/data/web/inc/functions.relayhost.inc.php b/data/web/inc/functions.relayhost.inc.php deleted file mode 100644 index a3e1ffda..00000000 --- a/data/web/inc/functions.relayhost.inc.php +++ /dev/null @@ -1,174 +0,0 @@ -<?php -function relayhost($_action, $_data = null) { - global $pdo; - global $lang; - $_data_log = $_data; - switch ($_action) { - case 'add': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $hostname = trim($_data['hostname']); - $username = str_replace(':', '\:', trim($_data['username'])); - $password = str_replace(':', '\:', trim($_data['password'])); - if (empty($hostname)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_host', htmlspecialchars($host)) - ); - return false; - } - try { - $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`) - VALUES (:hostname, :username, :password, :active)"); - $stmt->execute(array( - ':hostname' => $hostname, - ':username' => $username, - ':password' => str_replace(':', '\:', $password), - ':active' => '1' - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts))) - ); - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = relayhost('details', $id); - if (!empty($is_now)) { - $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname']; - $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; - $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_invalid', $id) - ); - continue; - } - try { - $stmt = $pdo->prepare("UPDATE `relayhosts` SET - `hostname` = :hostname, - `username` = :username, - `password` = :password, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id, - ':hostname' => $hostname, - ':username' => $username, - ':password' => $password, - ':active' => $active - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames))) - ); - } - break; - case 'delete': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - try { - $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id"); - $stmt->execute(array(':id' => $id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_removed', htmlspecialchars($id)) - ); - } - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $relayhosts = array(); - $stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`"); - $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $relayhosts; - break; - case 'details': - if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { - return false; - } - $relayhostdata = array(); - $stmt = $pdo->prepare("SELECT `id`, - `hostname`, - `username`, - `password`, - `active` AS `active_int`, - CONCAT(LEFT(`password`, 3), '...') AS `password_short`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` - FROM `relayhosts` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($relayhostdata)) { - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id"); - $stmt->execute(array(':id' => $_data)); - $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains']; - $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains; - $relayhostdata['used_by_domains'] = $used_by_domains; - } - return $relayhostdata; - break; - } -} \ No newline at end of file diff --git a/data/web/inc/functions.tls_policy_maps.inc.php b/data/web/inc/functions.tls_policy_maps.inc.php new file mode 100644 index 00000000..70f9f8f0 --- /dev/null +++ b/data/web/inc/functions.tls_policy_maps.inc.php @@ -0,0 +1,157 @@ +<?php +function tls_policy_maps($_action, $_data = null, $attr = null) { + global $pdo; + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + switch ($_action) { + case 'add': + $dest = idn_to_ascii(trim($_data['dest']), 0, INTL_IDNA_VARIANT_UTS46); + $policy = strtolower(trim($_data['policy'])); + $parameters = (isset($_data['parameters']) && !empty($_data['parameters'])) ? $_data['parameters'] : ''; + if (!empty($parameters)) { + foreach (explode(' ', $parameters) as $parameter) { + if (!preg_match('/(.+)\=(.+)/i', $parameter)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'tls_policy_map_parameter_invalid' + ); + return false; + } + } + } + $active = intval($_data['active']); + $tls_policy_maps = tls_policy_maps('get'); + foreach ($tls_policy_maps as $tls_policy_map) { + if (tls_policy_maps('details', $tls_policy_map)['dest'] == $dest) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('tls_policy_map_entry_exists', htmlspecialchars($dest)) + ); + return false; + } + } + $stmt = $pdo->prepare("INSERT INTO `tls_policy_override` (`dest`, `policy`, `parameters`, `active`) VALUES + (:dest, :policy, :parameters, :active)"); + $stmt->execute(array( + ':dest' => $dest, + ':policy' => $policy, + ':parameters' => $parameters, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('tls_policy_map_entry_saved', htmlspecialchars($dest)) + ); + break; + case 'edit': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = tls_policy_maps('details', $id); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $dest = (!empty($_data['dest'])) ? $_data['dest'] : $is_now['dest']; + $policy = (!empty($_data['policy'])) ? $_data['policy'] : $is_now['policy']; + $parameters = (isset($_data['parameters'])) ? $_data['parameters'] : $is_now['parameters']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!empty($parameters)) { + foreach (explode(' ', $parameters) as $parameter) { + if (!preg_match('/(.+)\=(.+)/i', $parameter)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'tls_policy_map_parameter_invalid' + ); + return false; + } + } + } + $tls_policy_maps = tls_policy_maps('get'); + foreach ($tls_policy_maps as $tls_policy_map) { + if ($tls_policy_map == $id) { continue; } + if (tls_policy_maps('details', $tls_policy_map)['dest'] == $dest) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_exists', htmlspecialchars($dest)) + ); + return false; + } + } + $stmt = $pdo->prepare("UPDATE `tls_policy_override` SET + `dest` = :dest, + `policy` = :policy, + `parameters` = :parameters, + `active` = :active + WHERE `id`= :id"); + $stmt->execute(array( + ':dest' => $dest, + ':policy' => $policy, + ':parameters' => $parameters, + ':active' => $active, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('tls_policy_map_entry_saved', htmlspecialchars($dest)) + ); + } + break; + case 'details': + $mapdata = array(); + $id = intval($_data); + $stmt = $pdo->prepare("SELECT `id`, + `dest`, + `policy`, + `parameters`, + `active` AS `active_int`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `created`, + `modified` FROM `tls_policy_override` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $id)); + $mapdata = $stmt->fetch(PDO::FETCH_ASSOC); + return $mapdata; + break; + case 'get': + $mapdata = array(); + $all_items = array(); + $id = intval($_data); + $stmt = $pdo->query("SELECT `id` FROM `tls_policy_override`"); + $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($all_items as $i) { + $mapdata[] = $i['id']; + } + $all_items = null; + return $mapdata; + break; + case 'delete': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + return false; + } + $stmt = $pdo->prepare("DELETE FROM `tls_policy_override` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('tls_policy_map_entry_deleted', htmlspecialchars($id)) + ); + } + break; + } +} diff --git a/data/web/inc/functions.transports.inc.php b/data/web/inc/functions.transports.inc.php new file mode 100644 index 00000000..e45fab78 --- /dev/null +++ b/data/web/inc/functions.transports.inc.php @@ -0,0 +1,449 @@ +<?php +function relayhost($_action, $_data = null) { + global $pdo; + global $lang; + $_data_log = $_data; + switch ($_action) { + case 'add': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $hostname = trim($_data['hostname']); + $username = str_replace(':', '\:', trim($_data['username'])); + $password = str_replace(':', '\:', trim($_data['password'])); + if (empty($hostname)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_host', htmlspecialchars($host)) + ); + return false; + } + try { + $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`) + VALUES (:hostname, :username, :password, :active)"); + $stmt->execute(array( + ':hostname' => $hostname, + ':username' => $username, + ':password' => str_replace(':', '\:', $password), + ':active' => '1' + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = relayhost('details', $id); + if (!empty($is_now)) { + $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname']; + $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_invalid', $id) + ); + continue; + } + try { + $stmt = $pdo->prepare("UPDATE `relayhosts` SET + `hostname` = :hostname, + `username` = :username, + `password` = :password, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':hostname' => $hostname, + ':username' => $username, + ':password' => $password, + ':active' => $active + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + try { + $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id"); + $stmt->execute(array(':id' => $id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $relayhosts = array(); + $stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`"); + $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $relayhosts; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $relayhostdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `hostname`, + `username`, + `password`, + `active` AS `active_int`, + CONCAT(LEFT(`password`, 3), '...') AS `password_short`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + FROM `relayhosts` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($relayhostdata)) { + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id"); + $stmt->execute(array(':id' => $_data)); + $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains']; + $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains; + $relayhostdata['used_by_domains'] = $used_by_domains; + } + return $relayhostdata; + break; + } +} +function transport($_action, $_data = null) { + global $pdo; + global $lang; + $_data_log = $_data; + switch ($_action) { + case 'add': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $destination = trim($_data['destination']); + $nexthop = trim($_data['nexthop']); + preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches); + $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop; + $username = str_replace(':', '\:', trim($_data['username'])); + $password = str_replace(':', '\:', trim($_data['password'])); + // ".domain" is a valid destination, "..domain" is not + if (empty($destination) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $destination)) === false && $destination != '*')) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'invalid_destination' + ); + return false; + } + if (empty($nexthop)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_nexthop') + ); + return false; + } + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + $transport_data = transport('details', $transport['id']); + $existing_nh[] = $transport_data['nexthop']; + preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]); + if (($transport_data['nexthop'] == $nexthop || $transport_data['nexthop'] == $next_hop_clean) && $transport_data['username'] != $username) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'invalid_nexthop_authenticated' + ); + return false; + } + } + } + if (isset($next_hop_matches[1])) { + if (in_array($next_hop_clean, $existing_nh)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop) + ); + return false; + } + } + else { + foreach ($existing_clean_nh as $existing_clean_nh_each) { + if ($existing_clean_nh_each[1] == $nexthop) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes_any', $nexthop) + ); + return false; + } + } + } + try { + $stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `username` ,`password`, `active`) + VALUES (:nexthop, :destination, :username, :password, :active)"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':destination' => $destination, + ':username' => $username, + ':password' => str_replace(':', '\:', $password), + ':active' => '1' + )); + $stmt = $pdo->prepare("UPDATE `transports` SET + `username` = :username, + `password` = :password + WHERE `nexthop` = :nexthop"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = transport('details', $id); + if (!empty($is_now)) { + $destination = (!empty($_data['destination'])) ? trim($_data['destination']) : $is_now['destination']; + $nexthop = (!empty($_data['nexthop'])) ? trim($_data['nexthop']) : $is_now['nexthop']; + $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_invalid', $id) + ); + continue; + } + preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches); + $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop; + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + $transport_data = transport('details', $transport['id']); + if ($transport['id'] == $id) { + continue; + } + $existing_nh[] = $transport_data['nexthop']; + preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]); + } + } + if (isset($next_hop_matches[1])) { + if (in_array($next_hop_clean, $existing_nh)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop) + ); + return false; + } + } + else { + foreach ($existing_clean_nh as $existing_clean_nh_each) { + if ($existing_clean_nh_each[1] == $nexthop) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes_any', $nexthop) + ); + return false; + } + } + } + if (empty($username)) { + $password = ''; + } + try { + $stmt = $pdo->prepare("UPDATE `transports` SET + `destination` = :destination, + `nexthop` = :nexthop, + `username` = :username, + `password` = :password, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':destination' => $destination, + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password, + ':active' => $active + )); + $stmt = $pdo->prepare("UPDATE `transports` SET + `username` = :username, + `password` = :password + WHERE `nexthop` = :nexthop"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + try { + $stmt = $pdo->prepare("DELETE FROM `transports` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $transports = array(); + $stmt = $pdo->query("SELECT `id`, `destination`, `nexthop`, `username` FROM `transports`"); + $transports = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $transports; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $transportdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `destination`, + `nexthop`, + `username`, + `password`, + `active` AS `active_int`, + CONCAT(LEFT(`password`, 3), '...') AS `password_short`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + FROM `transports` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $transportdata = $stmt->fetch(PDO::FETCH_ASSOC); + return $transportdata; + break; + } +} \ No newline at end of file diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 46384b7b..47e53dc5 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -1,145 +1,145 @@ <!DOCTYPE html> <html lang="<?= $_SESSION['mailcow_locale'] ?>"> <head> -<meta charset="utf-8"> -<meta http-equiv="X-UA-Compatible" content="IE=edge"> -<meta name="viewport" content="width=device-width, initial-scale=1"> -<meta name="theme-color" content="#F5D76E"/> -<meta http-equiv="Referrer-Policy" content="same-origin"> -<title><?=$UI_TEXTS['title_name'];?></title> -<!--[if lt IE 9]> - <script src="/js/html5shiv.min.js"></script> - <script src="/js/respond.min.js"></script> -<![endif]--> -<script src="/js/jquery-1.12.4.min.js"></script> -<?php if (strtolower(trim($DEFAULT_THEME)) != "lumen"): ?> -<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/<?= strtolower(trim($DEFAULT_THEME)); ?>/bootstrap.min.css"> -<?php else: ?> -<link rel="stylesheet" href="/css/bootstrap.min.css"> -<?php endif; ?> -<link rel="stylesheet" href="/css/breakpoint.min.css"> -<link rel="stylesheet" href="/css/bootstrap-select.min.css"> -<link rel="stylesheet" href="/css/bootstrap-slider.min.css"> -<link rel="stylesheet" href="/css/bootstrap-switch.min.css"> -<link rel="stylesheet" href="/css/footable.bootstrap.min.css"> -<link rel="stylesheet" href="/inc/languages.min.css"> -<link rel="stylesheet" href="/css/mailcow.css"> -<link rel="stylesheet" href="/css/animate.min.css"> -<link rel="stylesheet" href="/css/numberedtextarea.min.css"> -<link rel="stylesheet" href="/css/jquery.jqplot.min.css"> -<?= (preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/mailbox.css">' : null; ?> -<?= (preg_match("/admin.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/admin.css">' : null; ?> -<?= (preg_match("/user.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/user.css">' : null; ?> -<?= (preg_match("/edit.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/edit.css">' : null; ?> -<?= (preg_match("/quarantine.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/quarantine.css">' : null; ?> -<?= (preg_match("/debug.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/debug.css">' : null; ?> -<link rel="shortcut icon" href="/favicon.png" type="image/png"> -<link rel="icon" href="/favicon.png" type="image/png"> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="theme-color" content="#F5D76E"/> + <meta http-equiv="Referrer-Policy" content="same-origin"> + <title><?=$UI_TEXTS['title_name'];?></title> + <?php + if (preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/mailbox.css'); + } + if (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/admin.css'); + } + if (preg_match("/user/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/user.css'); + } + if (preg_match("/edit/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/edit.css'); + } + if (preg_match("/quarantine/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/quarantine.css'); + } + if (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/debug.css'); + } + ?> + <style><?=$css_minifier->minify();?></style> + <?php if (strtolower(trim($DEFAULT_THEME)) != "lumen"): ?> + <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/<?= strtolower(trim($DEFAULT_THEME)); ?>/bootstrap.min.css"> + <?php endif; ?> + <link rel="shortcut icon" href="/favicon.png" type="image/png"> + <link rel="icon" href="/favicon.png" type="image/png"> </head> <body id="top"> -<div class="overlay"></div> -<nav class="navbar navbar-default navbar-fixed-top" role="navigation"> - <div class="container-fluid"> - <div class="navbar-header"> - <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> - <span class="icon-bar"></span> - <span class="icon-bar"></span> - <span class="icon-bar"></span> - </button> - <a class="navbar-brand" href="/"><img alt="mailcow-logo" src="<?=($main_logo = customize('get', 'main_logo')) ? $main_logo : '/img/cow_mailcow.svg';?>"></a> - </div> - <div id="navbar" class="navbar-collapse collapse"> - <ul class="nav navbar-nav navbar-right"> - <?php - if (isset($_SESSION['mailcow_locale'])) { - ?> - <li class="dropdown<?=(isset($_SESSION['mailcow_locale']) && count($AVAILABLE_LANGUAGES) === 1) ? ' lang-link-disabled"' : '' ?>"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="lang-sm lang-lbl" lang="<?= $_SESSION['mailcow_locale']; ?>"></span><span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"> - <?php - foreach ($AVAILABLE_LANGUAGES as $language) { - ?> - <li<?= ($_SESSION['mailcow_locale'] == $language) ? ' class="active"' : ''; ?>><a href="?<?= http_build_query(array_merge($_GET, array('lang' => $language))); ?>"><span class="lang-xs lang-lbl-full" lang="<?= $language; ?>"></span></a></li> - <?php - } - ?> - </ul> - </li> - <?php - } - if (isset($_SESSION['mailcow_cc_role'])) { - ?> - <li class="dropdown"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><?= $lang['header']['mailcow_settings']; ?> <span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"> - <?php - if (isset($_SESSION['mailcow_cc_role'])) { - if ($_SESSION['mailcow_cc_role'] == 'admin') { - ?> - <li<?= (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/admin.php"><?= $lang['header']['administration']; ?></a></li> - <li<?= (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/debug.php"><?= $lang['header']['debug']; ?></a></li> - <?php - } - if ($_SESSION['mailcow_cc_role'] == 'admin' || $_SESSION['mailcow_cc_role'] == 'domainadmin') { - ?> - <li<?= (preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/mailbox.php"><?= $lang['header']['mailboxes']; ?></a></li> - <?php - } - if ($_SESSION['mailcow_cc_role'] != 'admin') { - ?> - <li<?= (preg_match("/user/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/user.php"><?= $lang['header']['user_settings']; ?></a></li> - <?php - } - } - ?> - </ul> - </li> - <?php - if (isset($_SESSION['mailcow_cc_role'])) { - ?> - <li<?= (preg_match("/quarantine/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/quarantine.php"><span class="glyphicon glyphicon-briefcase"></span> <?= $lang['header']['quarantine']; ?></a></li> - <?php - } - if ($_SESSION['mailcow_cc_role'] == 'admin') { - ?> - <li><a href data-toggle="modal" data-container="sogo-mailcow" data-target="#RestartContainer"><span class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_sogo']; ?></a></li> - <?php - } - ?> - <li class="dropdown"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="glyphicon glyphicon-link"></span> Apps <span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"> + <div class="overlay"></div> + <nav class="navbar navbar-default navbar-fixed-top" role="navigation"> + <div class="container-fluid"> + <div class="navbar-header"> + <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + <a class="navbar-brand" href="/"><img alt="mailcow-logo" src="<?=($main_logo = customize('get', 'main_logo')) ? $main_logo : '/img/cow_mailcow.svg';?>"></a> + </div> + <div id="navbar" class="navbar-collapse collapse"> + <ul class="nav navbar-nav navbar-right"> <?php - foreach ($MAILCOW_APPS as $app): + if (isset($_SESSION['mailcow_locale'])) { ?> - <li title="<?= htmlspecialchars($app['description']); ?>"><a href="<?= htmlspecialchars($app['link']); ?>"><?= htmlspecialchars($app['name']); ?></a></li> + <li class="dropdown<?=(isset($_SESSION['mailcow_locale']) && count($AVAILABLE_LANGUAGES) === 1) ? ' lang-link-disabled"' : '' ?>"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="lang-sm lang-lbl" lang="<?= $_SESSION['mailcow_locale']; ?>"></span><span class="caret"></span></a> + <ul class="dropdown-menu" role="menu"> + <?php + foreach ($AVAILABLE_LANGUAGES as $language) { + ?> + <li<?= ($_SESSION['mailcow_locale'] == $language) ? ' class="active"' : ''; ?>><a href="?<?= http_build_query(array_merge($_GET, array('lang' => $language))); ?>"><span class="lang-xs lang-lbl-full" lang="<?= $language; ?>"></span></a></li> + <?php + } + ?> + </ul> + </li> <?php - endforeach; - $app_links = customize('get', 'app_links'); - foreach ($app_links as $row) { - foreach ($row as $key => $val): + } + if (isset($_SESSION['mailcow_cc_role'])) { ?> - <li><a href="<?= htmlspecialchars($val); ?>"><?= htmlspecialchars($key); ?></a></li> + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><?= $lang['header']['mailcow_settings']; ?> <span class="caret"></span></a> + <ul class="dropdown-menu" role="menu"> + <?php + if (isset($_SESSION['mailcow_cc_role'])) { + if ($_SESSION['mailcow_cc_role'] == 'admin') { + ?> + <li<?= (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/admin"><?= $lang['header']['administration']; ?></a></li> + <li<?= (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/debug"><?= $lang['header']['debug']; ?></a></li> + <?php + } + if ($_SESSION['mailcow_cc_role'] == 'admin' || $_SESSION['mailcow_cc_role'] == 'domainadmin') { + ?> + <li<?= (preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/mailbox"><?= $lang['header']['mailboxes']; ?></a></li> + <?php + } + if ($_SESSION['mailcow_cc_role'] != 'admin') { + ?> + <li<?= (preg_match("/user/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/user"><?= $lang['header']['user_settings']; ?></a></li> + <?php + } + } + ?> + </ul> + </li> + <?php + if (isset($_SESSION['mailcow_cc_role'])) { + ?> + <li<?= (preg_match("/quarantine/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/quarantine"><span class="glyphicon glyphicon-briefcase"></span> <?= $lang['header']['quarantine']; ?></a></li> + <?php + } + if ($_SESSION['mailcow_cc_role'] == 'admin') { + ?> + <li><a href data-toggle="modal" data-container="sogo-mailcow" data-target="#RestartContainer"><span class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_sogo']; ?></a></li> <?php - endforeach; } ?> - </ul> - </li> - <?php - } - if (!isset($_SESSION['dual-login']) && isset($_SESSION['mailcow_cc_username'])): - ?> - <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?></b> <span class="glyphicon glyphicon-log-out"></span></a></li> - <?php - elseif (isset($_SESSION['dual-login'])): - ?> - <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?> <span class="text-info">(<?= htmlspecialchars($_SESSION['dual-login']['username']); ?>)</span> </b><span class="glyphicon glyphicon-log-out"></span></a></li> - <?php - endif; - ?> - </ul> - </div><!--/.nav-collapse --> - </div><!--/.container-fluid --> -</nav> -<form action="/" method="post" id="logout"><input type="hidden" name="logout"></form> + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="glyphicon glyphicon-link"></span> <?= $lang['header']['apps']; ?> <span class="caret"></span></a> + <ul class="dropdown-menu" role="menu"> + <?php + foreach ($MAILCOW_APPS as $app): + ?> + <li title="<?= htmlspecialchars($app['description']); ?>"><a href="<?= htmlspecialchars($app['link']); ?>"><?= htmlspecialchars($app['name']); ?></a></li> + <?php + endforeach; + $app_links = customize('get', 'app_links'); + if ($app_links) { + foreach ($app_links as $row) { + foreach ($row as $key => $val): + ?> + <li><a href="<?= htmlspecialchars($val); ?>"><?= htmlspecialchars($key); ?></a></li> + <?php + endforeach; + } + } + ?> + </ul> + </li> + <?php + } + if (!isset($_SESSION['dual-login']) && isset($_SESSION['mailcow_cc_username'])): + ?> + <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?></b> <span class="glyphicon glyphicon-log-out"></span></a></li> + <?php + elseif (isset($_SESSION['dual-login'])): + ?> + <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?> <span class="text-info">(<?= htmlspecialchars($_SESSION['dual-login']['username']); ?>)</span> </b><span class="glyphicon glyphicon-log-out"></span></a></li> + <?php + endif; + ?> + </ul> + </div><!--/.nav-collapse --> + </div><!--/.container-fluid --> + </nav> + <form action="/" method="post" id="logout"><input type="hidden" name="logout"></form> diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index c318d634..3c553bfd 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "19082018_1004"; + $db_version = "30032019_1905"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -78,7 +78,6 @@ function init_db_schema() { // TODO -> use TEXT and check if SOGo login breaks on empty aliases "aliases" => "TEXT NOT NULL", "ad_aliases" => "VARCHAR(6144) NOT NULL DEFAULT ''", - "home" => "VARCHAR(255)", "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", "multiple_bookings" => "INT NOT NULL DEFAULT -1" ), @@ -110,6 +109,26 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), + "transports" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "destination" => "VARCHAR(255) NOT NULL", + "nexthop" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "destination" => array("destination"), + "nexthop" => array("nexthop"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), "alias" => array( "cols" => array( "id" => "INT NOT NULL AUTO_INCREMENT", @@ -118,6 +137,8 @@ function init_db_schema() { "domain" => "VARCHAR(255) NOT NULL", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "private_comment" => "TEXT", + "public_comment" => "TEXT", "active" => "TINYINT(1) NOT NULL DEFAULT '1'" ), "keys" => array( @@ -135,7 +156,6 @@ function init_db_schema() { ), "api" => array( "cols" => array( - "username" => "VARCHAR(255) NOT NULL", "api_key" => "VARCHAR(255) NOT NULL", "allow_from" => "VARCHAR(512) NOT NULL", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", @@ -144,16 +164,8 @@ function init_db_schema() { ), "keys" => array( "primary" => array( - "" => array("username") + "" => array("api_key") ), - "fkey" => array( - "fk_username_api" => array( - "col" => "username", - "ref" => "admin.username", - "delete" => "CASCADE", - "update" => "CASCADE" - ) - ) ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), @@ -171,6 +183,7 @@ function init_db_schema() { "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), "domain" => array( + // Todo: Move some attributes to json "cols" => array( "domain" => "VARCHAR(255) NOT NULL", "description" => "VARCHAR(255)", @@ -180,6 +193,7 @@ function init_db_schema() { "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", "relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'", "backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'", + "gal" => "TINYINT(1) NOT NULL DEFAULT '1'", "relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", @@ -192,18 +206,40 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), + "tls_policy_override" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "dest" => "VARCHAR(255) NOT NULL", + "policy" => "ENUM('none', 'may', 'encrypt', 'dane', 'dane-only', 'fingerprint', 'verify', 'secure') NOT NULL", + "parameters" => "VARCHAR(255) DEFAULT ''", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "unique" => array( + "dest" => array("dest") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), "quarantine" => array( "cols" => array( "id" => "INT NOT NULL AUTO_INCREMENT", "qid" => "VARCHAR(30) NOT NULL", + "subject" => "VARCHAR(500)", "score" => "FLOAT(8,2)", - "ip" => "VARBINARY(16)", + "ip" => "VARCHAR(50)", "action" => "CHAR(20) NOT NULL DEFAULT 'unknown'", "symbols" => "JSON", "sender" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", "rcpt" => "VARCHAR(255)", "msg" => "LONGTEXT", "domain" => "VARCHAR(255)", + "notified" => "TINYINT(1) NOT NULL DEFAULT '0'", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", ), @@ -219,7 +255,8 @@ function init_db_schema() { "username" => "VARCHAR(255) NOT NULL", "password" => "VARCHAR(255) NOT NULL", "name" => "VARCHAR(255)", - "maildir" => "VARCHAR(255) NOT NULL", + // mailbox_path_prefix is followed by domain/local_part/ + "mailbox_path_prefix" => "VARCHAR(150) DEFAULT '/var/vmail/'", "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", "local_part" => "VARCHAR(255) NOT NULL", "domain" => "VARCHAR(255) NOT NULL", @@ -280,10 +317,10 @@ function init_db_schema() { "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", - "filters" => "TINYINT(1) NOT NULL DEFAULT '1'", + "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", - "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", - "recipient_maps" => "TINYINT(1) NOT NULL DEFAULT '0'", + "quarantine_attachments" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'", ), "keys" => array( "primary" => array( @@ -417,6 +454,26 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), + "da_acl" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", + "login_as" => "TINYINT(1) NOT NULL DEFAULT '1'", + "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", + "filters" => "TINYINT(1) NOT NULL DEFAULT '1'", + "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'", + "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", + "unlimited_quota" => "TINYINT(1) NOT NULL DEFAULT '0'", + "alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'", + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), "imapsync" => array( "cols" => array( "id" => "INT NOT NULL AUTO_INCREMENT", @@ -935,30 +992,88 @@ DELIMITER ;'; // Insert new DB schema version $stmt = $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');"); - // Migrate tls_enforce_* options and add force_pw_update attribute - $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` IS NULL;"); - $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', 0) WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;"); + // Migrate attributes + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.sogo_access') IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_EXTRACT(`attributes`, '$.mailbox_format') IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_EXTRACT(`attributes`, '$.quarantine_notification') IS NULL;"); foreach($tls_options as $tls_user => $tls_options) { $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) WHERE `username` = :username"); $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__), - 'msg' => 'db_init_complete' - ); - - // Fix user_acl + // Set tls_enforce_* if still missing (due to deleted attrs for example) + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.tls_enforce_out') IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.tls_enforce_in') IS NULL;"); + // Fix ACL $stmt = $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);"); + $stmt = $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);"); + // Fix domain_admins + $stmt = $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); + + if (php_sapi_name() == "cli") { + echo "DB initialization completed" . PHP_EOL; + } else { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__), + 'msg' => 'db_init_complete' + ); + } } catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => array('mysql_error', $e) - ); + if (php_sapi_name() == "cli") { + echo "DB initialization failed: " . print_r($e, true) . PHP_EOL; + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => array('mysql_error', $e) + ); + } } } -?> +if (php_sapi_name() == "cli") { + include '/web/inc/vars.inc.php'; + // $now = new DateTime(); + // $mins = $now->getOffset() / 60; + // $sgn = ($mins < 0 ? -1 : 1); + // $mins = abs($mins); + // $hrs = floor($mins / 60); + // $mins -= $hrs * 60; + // $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); + $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; + $opt = [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", + ]; + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); + $stmt = $pdo->query("SELECT COUNT('OK') AS OK_C FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view' OR TABLE_NAME = '_sogo_static_view';"); + $res = $stmt->fetch(PDO::FETCH_ASSOC); + if (intval($res['OK_C']) === 2) { + // Be more precise when replacing into _sogo_static_view, col orders may change + try { + $stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `kind`, `multiple_bookings`) + SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `kind`, `multiple_bookings` from sogo_view"); + $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); + echo "Fixed _sogo_static_view" . PHP_EOL; + } + catch ( Exception $e ) { + // Dunno + } + } + try { + $m = new Memcached(); + $m->addServer('memcached', 11211); + $m->flush(); + echo "Cleaned up memcached". PHP_EOL; + } + catch ( Exception $e ) { + // Dunno + } + init_db_schema(); +} diff --git a/data/web/inc/languages.min.css b/data/web/inc/languages.min.css deleted file mode 100644 index 7293e888..00000000 --- a/data/web/inc/languages.min.css +++ /dev/null @@ -1 +0,0 @@ -.lang-lg,.lang-sm,.lang-xs{background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-sm,.lang-sm:after,.lang-xs,.lang-xs:after{position:relative}.lang-xs{background-position:0 -484px;min-width:14px;height:11px;min-height:11px;max-height:11px}.lang-sm{background-position:0 -1199px;min-width:22px;height:16px;min-height:16px;max-height:16px}.lang-lg{background-position:0 -2134px;min-width:30px;height:22px;min-height:22px;max-height:22px}.lang-xs[lang=ar]{background-position:0 0}.lang-xs[lang=be]{background-position:0 -11px}.lang-xs[lang=bg]{background-position:0 -22px}.lang-xs[lang=cs]{background-position:0 -33px}.lang-xs[lang=da]{background-position:0 -44px}.lang-xs[lang=de]{background-position:0 -55px}.lang-xs[lang=el]{background-position:0 -66px}.lang-xs[lang=en]{background-position:0 -77px}.lang-xs[lang=es]{background-position:0 -88px}.lang-xs[lang=et]{background-position:0 -99px}.lang-xs[lang=fi]{background-position:0 -110px}.lang-xs[lang=fr]{background-position:0 -121px}.lang-xs[lang=ga]{background-position:0 -132px}.lang-xs[lang=hi]{background-position:0 -143px}.lang-xs[lang=hr]{background-position:0 -154px}.lang-xs[lang=hu]{background-position:0 -165px}.lang-xs[lang=in]{background-position:0 -176px}.lang-xs[lang=is]{background-position:0 -187px}.lang-xs[lang=it]{background-position:0 -198px}.lang-xs[lang=iw]{background-position:0 -209px}.lang-xs[lang=ja]{background-position:0 -220px}.lang-xs[lang=ko]{background-position:0 -231px}.lang-xs[lang=lt]{background-position:0 -242px}.lang-xs[lang=lv]{background-position:0 -253px}.lang-xs[lang=mk]{background-position:0 -264px}.lang-xs[lang=ms]{background-position:0 -275px}.lang-xs[lang=mt]{background-position:0 -286px}.lang-xs[lang=nl]{background-position:0 -297px}.lang-xs[lang=no]{background-position:0 -308px}.lang-xs[lang=pl]{background-position:0 -319px}.lang-xs[lang=pt]{background-position:0 -330px}.lang-xs[lang=ro]{background-position:0 -341px}.lang-xs[lang=ru]{background-position:0 -352px}.lang-xs[lang=sk]{background-position:0 -363px}.lang-xs[lang=sl]{background-position:0 -374px}.lang-xs[lang=sq]{background-position:0 -385px}.lang-xs[lang=sr]{background-position:0 -396px}.lang-xs[lang=sv]{background-position:0 -407px}.lang-xs[lang=th]{background-position:0 -418px}.lang-xs[lang=tr]{background-position:0 -429px}.lang-xs[lang=uk]{background-position:0 -440px}.lang-xs[lang=vi]{background-position:0 -451px}.lang-xs[lang=zh]{background-position:0 -462px}.lang-xs[lang=ca]{background-position:0 -473px}.lang-sm[lang=ar]{background-position:0 -495px}.lang-sm[lang=be]{background-position:0 -511px}.lang-sm[lang=bg]{background-position:0 -527px}.lang-sm[lang=cs]{background-position:0 -543px}.lang-sm[lang=da]{background-position:0 -559px}.lang-sm[lang=de]{background-position:0 -575px}.lang-sm[lang=el]{background-position:0 -591px}.lang-sm[lang=en]{background-position:0 -607px}.lang-sm[lang=es]{background-position:0 -623px}.lang-sm[lang=et]{background-position:0 -639px}.lang-sm[lang=fi]{background-position:0 -655px}.lang-sm[lang=fr]{background-position:0 -671px}.lang-sm[lang=ga]{background-position:0 -687px}.lang-sm[lang=hi]{background-position:0 -703px}.lang-sm[lang=hr]{background-position:0 -719px}.lang-sm[lang=hu]{background-position:0 -735px}.lang-sm[lang=in]{background-position:0 -751px}.lang-sm[lang=is]{background-position:0 -767px}.lang-sm[lang=it]{background-position:0 -783px}.lang-sm[lang=iw]{background-position:0 -799px}.lang-sm[lang=ja]{background-position:0 -815px}.lang-sm[lang=ko]{background-position:0 -831px}.lang-sm[lang=lt]{background-position:0 -847px}.lang-sm[lang=lv]{background-position:0 -863px}.lang-sm[lang=mk]{background-position:0 -879px}.lang-sm[lang=ms]{background-position:0 -895px}.lang-sm[lang=mt]{background-position:0 -911px}.lang-sm[lang=nl]{background-position:0 -927px}.lang-sm[lang=no]{background-position:0 -943px}.lang-sm[lang=pl]{background-position:0 -959px}.lang-sm[lang=pt]{background-position:0 -975px}.lang-sm[lang=ro]{background-position:0 -991px}.lang-sm[lang=ru]{background-position:0 -1007px}.lang-sm[lang=sk]{background-position:0 -1023px}.lang-sm[lang=sl]{background-position:0 -1039px}.lang-sm[lang=sq]{background-position:0 -1055px}.lang-sm[lang=sr]{background-position:0 -1071px}.lang-sm[lang=sv]{background-position:0 -1087px}.lang-sm[lang=th]{background-position:0 -1103px}.lang-sm[lang=tr]{background-position:0 -1119px}.lang-sm[lang=uk]{background-position:0 -1135px}.lang-sm[lang=vi]{background-position:0 -1151px}.lang-sm[lang=zh]{background-position:0 -1167px}.lang-sm[lang=ca]{background-position:0 -1183px}.lang-lg[lang=ar]{background-position:0 -1188px}.lang-lg[lang=be]{background-position:0 -1210px}.lang-lg[lang=bg]{background-position:0 -1232px}.lang-lg[lang=cs]{background-position:0 -1254px}.lang-lg[lang=da]{background-position:0 -1276px}.lang-lg[lang=de]{background-position:0 -1298px}.lang-lg[lang=el]{background-position:0 -1320px}.lang-lg[lang=en]{background-position:0 -1342px}.lang-lg[lang=es]{background-position:0 -1364px}.lang-lg[lang=et]{background-position:0 -1386px}.lang-lg[lang=fi]{background-position:0 -1408px}.lang-lg[lang=fr]{background-position:0 -1430px}.lang-lg[lang=ga]{background-position:0 -1452px}.lang-lg[lang=hi]{background-position:0 -1474px}.lang-lg[lang=hr]{background-position:0 -1496px}.lang-lg[lang=hu]{background-position:0 -1518px}.lang-lg[lang=in]{background-position:0 -1540px}.lang-lg[lang=is]{background-position:0 -1562px}.lang-lg[lang=it]{background-position:0 -1584px}.lang-lg[lang=iw]{background-position:0 -1606px}.lang-lg[lang=ja]{background-position:0 -1628px}.lang-lg[lang=ko]{background-position:0 -1650px}.lang-lg[lang=lt]{background-position:0 -1672px}.lang-lg[lang=lv]{background-position:0 -1694px}.lang-lg[lang=mk]{background-position:0 -1716px}.lang-lg[lang=ms]{background-position:0 -1738px}.lang-lg[lang=mt]{background-position:0 -1760px}.lang-lg[lang=nl]{background-position:0 -1782px}.lang-lg[lang=no]{background-position:0 -1804px}.lang-lg[lang=pl]{background-position:0 -1826px}.lang-lg[lang=pt]{background-position:0 -1848px}.lang-lg[lang=ro]{background-position:0 -1870px}.lang-lg[lang=ru]{background-position:0 -1892px}.lang-lg[lang=sk]{background-position:0 -1914px}.lang-lg[lang=sl]{background-position:0 -1936px}.lang-lg[lang=sq]{background-position:0 -1958px}.lang-lg[lang=sr]{background-position:0 -1980px}.lang-lg[lang=sv]{background-position:0 -2002px}.lang-lg[lang=th]{background-position:0 -2024px}.lang-lg[lang=tr]{background-position:0 -2046px}.lang-lg[lang=uk]{background-position:0 -2068px}.lang-lg[lang=vi]{background-position:0 -2090px}.lang-lg[lang=zh]{background-position:0 -2112px}.lang-lbl-en:after,.lang-lbl-full:after,.lang-lbl:after{content:"Unknown language"}.lang-lbl[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629"}.lang-lbl[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456"}.lang-lbl[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438"}.lang-lbl[lang=ca]:after{content:"Catal\0000E0"}.lang-lbl[lang=cs]:after{content:"\00010Ce\000161tina"}.lang-lbl[lang=da]:after{content:"Dansk"}.lang-lbl[lang=de]:after{content:"Deutsch"}.lang-lbl[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC"}.lang-lbl[lang=en]:after{content:"English"}.lang-lbl[lang=es]:after{content:"Espa\0000F1ol"}.lang-lbl[lang=et]:after{content:"Eesti"}.lang-lbl[lang=fi]:after{content:"Suomi"}.lang-lbl[lang=fr]:after{content:"Fran\0000E7ais"}.lang-lbl[lang=ga]:after{content:"Gaeilge"}.lang-lbl[lang=hi]:after{content:"\000939\00093F\000902\000926\000940"}.lang-lbl[lang=hr]:after{content:"Hrvatski"}.lang-lbl[lang=hu]:after{content:"Magyar"}.lang-lbl[lang=in]:after{content:"Bahasa\000020indonesia"}.lang-lbl[lang=is]:after{content:"\0000CDslenska"}.lang-lbl[lang=it]:after{content:"Italiano"}.lang-lbl[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA"}.lang-lbl[lang=ja]:after{content:"\0065E5\00672C\008A9E"}.lang-lbl[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4"}.lang-lbl[lang=lt]:after{content:"Lietuvi\000173"}.lang-lbl[lang=lv]:after{content:"Latvie\000161u"}.lang-lbl[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438"}.lang-lbl[lang=ms]:after{content:"Bahasa\000020melayu"}.lang-lbl[lang=mt]:after{content:"Malti"}.lang-lbl[lang=nl]:after{content:"Nederlands"}.lang-lbl[lang=no]:after{content:"Norsk"}.lang-lbl[lang=pl]:after{content:"Polski"}.lang-lbl[lang=pt]:after{content:"Portugu\0000EAs"}.lang-lbl[lang=ro]:after{content:"Rom\0000E2n\000103"}.lang-lbl[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439"}.lang-lbl[lang=sk]:after{content:"Sloven\00010Dina"}.lang-lbl[lang=sl]:after{content:"Sloven\000161\00010Dina"}.lang-lbl[lang=sq]:after{content:"Shqipe"}.lang-lbl[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438"}.lang-lbl[lang=sv]:after{content:"Svenska"}.lang-lbl[lang=th]:after{content:"\000E44\000E17\000E22"}.lang-lbl[lang=tr]:after{content:"T\0000FCrk\0000E7e"}.lang-lbl[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430"}.lang-lbl[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t"}.lang-lbl[lang=zh]:after{content:"\004E2D\006587"}.lang-lbl-en[lang=ar]:after{content:"Arabic"}.lang-lbl-en[lang=be]:after{content:"Belarusian"}.lang-lbl-en[lang=bg]:after{content:"Bulgarian"}.lang-lbl-en[lang=ca]:after{content:"Catalan"}.lang-lbl-en[lang=cs]:after{content:"Czech"}.lang-lbl-en[lang=da]:after{content:"Danish"}.lang-lbl-en[lang=de]:after{content:"German"}.lang-lbl-en[lang=el]:after{content:"Greek"}.lang-lbl-en[lang=en]:after{content:"English"}.lang-lbl-en[lang=es]:after{content:"Spanish"}.lang-lbl-en[lang=et]:after{content:"Estonian"}.lang-lbl-en[lang=fi]:after{content:"Finnish"}.lang-lbl-en[lang=fr]:after{content:"French"}.lang-lbl-en[lang=ga]:after{content:"Irish"}.lang-lbl-en[lang=hi]:after{content:"Hindi"}.lang-lbl-en[lang=hr]:after{content:"Croatian"}.lang-lbl-en[lang=hu]:after{content:"Hungarian"}.lang-lbl-en[lang=in]:after{content:"Indonesian"}.lang-lbl-en[lang=is]:after{content:"Icelandic"}.lang-lbl-en[lang=it]:after{content:"Italian"}.lang-lbl-en[lang=iw]:after{content:"Hebrew"}.lang-lbl-en[lang=ja]:after{content:"Japanese"}.lang-lbl-en[lang=ko]:after{content:"Korean"}.lang-lbl-en[lang=lt]:after{content:"Lithuanian"}.lang-lbl-en[lang=lv]:after{content:"Latvian"}.lang-lbl-en[lang=mk]:after{content:"Macedonian"}.lang-lbl-en[lang=ms]:after{content:"Malay"}.lang-lbl-en[lang=mt]:after{content:"Maltese"}.lang-lbl-en[lang=nl]:after{content:"Dutch"}.lang-lbl-en[lang=no]:after{content:"Norwegian"}.lang-lbl-en[lang=pl]:after{content:"Polish"}.lang-lbl-en[lang=pt]:after{content:"Portuguese"}.lang-lbl-en[lang=ro]:after{content:"Romanian"}.lang-lbl-en[lang=ru]:after{content:"Russian"}.lang-lbl-en[lang=sk]:after{content:"Slovak"}.lang-lbl-en[lang=sl]:after{content:"Slovenian"}.lang-lbl-en[lang=sq]:after{content:"Albanian"}.lang-lbl-en[lang=sr]:after{content:"Serbian"}.lang-lbl-en[lang=sv]:after{content:"Swedish"}.lang-lbl-en[lang=th]:after{content:"Thai"}.lang-lbl-en[lang=tr]:after{content:"Turkish"}.lang-lbl-en[lang=uk]:after{content:"Ukrainian"}.lang-lbl-en[lang=vi]:after{content:"Vietnamese"}.lang-lbl-en[lang=zh]:after{content:"Chinese"}.lang-lbl-full[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629\0000A0/\0000A0Arabic"}.lang-lbl-full[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456\0000A0/\0000A0Belarusian"}.lang-lbl-full[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438\0000A0/\0000A0Bulgarian"}.lang-lbl-full[lang=ca]:after{content:"Catal\0000E0\0000A0/\0000A0Catalan"}.lang-lbl-full[lang=cs]:after{content:"\00010Ce\000161tina\0000A0/\0000A0Czech"}.lang-lbl-full[lang=da]:after{content:"Dansk\0000A0/\0000A0Danish"}.lang-lbl-full[lang=de]:after{content:"Deutsch\0000A0/\0000A0German"}.lang-lbl-full[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC\0000A0/\0000A0Greek"}.lang-lbl-full[lang=en]:after{content:"English\0000A0/\0000A0English"}.lang-lbl-full[lang=es]:after{content:"Espa\0000F1ol\0000A0/\0000A0Spanish"}.lang-lbl-full[lang=et]:after{content:"Eesti\0000A0/\0000A0Estonian"}.lang-lbl-full[lang=fi]:after{content:"Suomi\0000A0/\0000A0Finnish"}.lang-lbl-full[lang=fr]:after{content:"Fran\0000E7ais\0000A0/\0000A0French"}.lang-lbl-full[lang=ga]:after{content:"Gaeilge\0000A0/\0000A0Irish"}.lang-lbl-full[lang=hi]:after{content:"\000939\00093F\000902\000926\000940\0000A0/\0000A0Hindi"}.lang-lbl-full[lang=hr]:after{content:"Hrvatski\0000A0/\0000A0Croatian"}.lang-lbl-full[lang=hu]:after{content:"Magyar\0000A0/\0000A0Hungarian"}.lang-lbl-full[lang=in]:after{content:"Bahasa\000020indonesia\0000A0/\0000A0Indonesian"}.lang-lbl-full[lang=is]:after{content:"\0000CDslenska\0000A0/\0000A0Icelandic"}.lang-lbl-full[lang=it]:after{content:"Italiano\0000A0/\0000A0Italian"}.lang-lbl-full[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA\0000A0/\0000A0Hebrew"}.lang-lbl-full[lang=ja]:after{content:"\0065E5\00672C\008A9E\0000A0/\0000A0Japanese"}.lang-lbl-full[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4\0000A0/\0000A0Korean"}.lang-lbl-full[lang=lt]:after{content:"Lietuvi\000173\0000A0/\0000A0Lithuanian"}.lang-lbl-full[lang=lv]:after{content:"Latvie\000161u\0000A0/\0000A0Latvian"}.lang-lbl-full[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438\0000A0/\0000A0Macedonian"}.lang-lbl-full[lang=ms]:after{content:"Bahasa\000020melayu\0000A0/\0000A0Malay"}.lang-lbl-full[lang=mt]:after{content:"Malti\0000A0/\0000A0Maltese"}.lang-lbl-full[lang=nl]:after{content:"Nederlands\0000A0/\0000A0Dutch"}.lang-lbl-full[lang=no]:after{content:"Norsk\0000A0/\0000A0Norwegian"}.lang-lbl-full[lang=pl]:after{content:"Polski\0000A0/\0000A0Polish"}.lang-lbl-full[lang=pt]:after{content:"Portugu\0000EAs\0000A0/\0000A0Portuguese"}.lang-lbl-full[lang=ro]:after{content:"Rom\0000E2n\000103\0000A0/\0000A0Romanian"}.lang-lbl-full[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439\0000A0/\0000A0Russian"}.lang-lbl-full[lang=sk]:after{content:"Sloven\00010Dina\0000A0/\0000A0Slovak"}.lang-lbl-full[lang=sl]:after{content:"Sloven\000161\00010Dina\0000A0/\0000A0Slovenian"}.lang-lbl-full[lang=sq]:after{content:"Shqipe\0000A0/\0000A0Albanian"}.lang-lbl-full[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438\0000A0/\0000A0Serbian"}.lang-lbl-full[lang=sv]:after{content:"Svenska\0000A0/\0000A0Swedish"}.lang-lbl-full[lang=th]:after{content:"\000E44\000E17\000E22\0000A0/\0000A0Thai"}.lang-lbl-full[lang=tr]:after{content:"T\0000FCrk\0000E7e\0000A0/\0000A0Turkish"}.lang-lbl-full[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430\0000A0/\0000A0Ukrainian"}.lang-lbl-full[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t\0000A0/\0000A0Vietnamese"}.lang-lbl-full[lang=zh]:after{content:"\004E2D\006587\0000A0/\0000A0Chinese"}.lang-lg:before,.lang-sm:before,.lang-xs:before{content:'\0000A0'}.lang-xs.lang-lbl,.lang-xs.lang-lbl-en,.lang-xs.lang-lbl-full{padding-left:16px}.lang-sm.lang-lbl,.lang-sm.lang-lbl-en,.lang-sm.lang-lbl-full{padding-left:24px}.lang-lg.lang-lbl,.lang-lg.lang-lbl-en,.lang-lg.lang-lbl-full{padding-left:32px}.lang-lg.lang-lbl-en:before,.lang-lg.lang-lbl-full:before,.lang-lg.lang-lbl:before,.lang-sm.lang-lbl-en:before,.lang-sm.lang-lbl-full:before,.lang-sm.lang-lbl:before,.lang-xs.lang-lbl-en:before,.lang-xs.lang-lbl-full:before,.lang-xs.lang-lbl:before{content:''}.lang-lg,.lang-lg:after{top:0;position:relative}.lang-sm{top:1px}.lang-sm:after{top:-1px}.lang-xs{top:4px}.lang-xs:after{top:-4px}.lead>.lang-lg{top:2px}.lead>.lang-lg:after{top:-2px}.lead>.lang-sm{top:6px}.lead>.lang-sm:after{top:-6px}.lead>.lang-xs{top:8px}.lead>.lang-xs:after{top:-8px}small>.lang-sm{top:-1px}small>.lang-sm:after{top:1px}small>.lang-xs{top:2px}small>.lang-xs:after{top:-2px}h1>.lang-lg{top:9px}h1>.lang-lg:after{top:-9px}h1>.lang-sm{top:12px}h1>.lang-sm:after{top:-12px}h1>.lang-xs{top:14px}h1>.lang-xs:after{top:-14px}h2>.lang-lg{top:5px}h2>.lang-lg:after{top:-5px}h2>.lang-sm{top:8px}h2>.lang-sm:after{top:-8px}h2>.lang-xs{top:10px}h2>.lang-xs:after{top:-10px}h3>.lang-lg{top:1px}h3>.lang-lg:after{top:-1px}h3>.lang-sm{top:5px}h3>.lang-sm:after{top:-5px}h3>.lang-xs{top:8px}h3>.lang-xs:after{top:-8px}h4>.lang-lg{top:-1px}h4>.lang-lg:after,h4>.lang-sm{top:1px}h4>.lang-sm:after{top:-1px}h4>.lang-xs{top:4px}h4>.lang-xs:after{top:-4px}h5>.lang-sm,h5>.lang-sm:after{top:0}h5>.lang-xs{top:2px}h5>.lang-xs:after{top:-2px}h6>.lang-sm,h6>.lang-sm:after{top:0}h6>.lang-xs{top:1px}h6>.lang-xs:after{top:-1px}.btn>.lang-sm{top:2px}.btn>.lang-sm:after{top:-2px}.btn>.lang-xs{top:4px}.btn>.lang-xs:after{top:-4px}.btn.btn-xs>.lang-sm,.btn.btn-xs>.lang-sm:after{top:0}.btn.btn-xs>.lang-xs{top:3px}.btn.btn-xs>.lang-xs:after{top:-3px}.btn.btn-sm>.lang-sm,.btn.btn-sm>.lang-sm:after{top:0}.btn.btn-sm>.lang-xs{top:3px}.btn.btn-sm>.lang-xs:after{top:-3px}.btn.btn-lg>.lang-lg{top:1px}.btn.btn-lg>.lang-lg:after{top:-1px}.btn.btn-lg>.lang-sm{top:3px}.btn.btn-lg>.lang-sm:after{top:-3px}.btn.btn-lg>.lang-xs{top:6px}.btn.btn-lg>.lang-xs:after{top:-6px} \ No newline at end of file diff --git a/data/web/inc/languages.png b/data/web/inc/languages.png deleted file mode 100644 index f5f1a2f0..00000000 Binary files a/data/web/inc/languages.png and /dev/null differ diff --git a/data/web/inc/lib/Yubico.php b/data/web/inc/lib/Yubico.php index 68a3087b..d8aa41c3 100644 --- a/data/web/inc/lib/Yubico.php +++ b/data/web/inc/lib/Yubico.php @@ -6,9 +6,9 @@ * @package Auth_Yubico * @author Simon Josefsson <simon@yubico.com>, Olov Danielson <olov@yubico.com> * @copyright 2007-2015 Yubico AB - * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @license https://opensource.org/licenses/bsd-license.php New BSD License * @version 2.0 - * @link http://www.yubico.com/ + * @link https://www.yubico.com/ */ require_once 'PEAR.php'; @@ -80,12 +80,6 @@ class Auth_Yubico */ var $_response; - /** - * Flag whether to use https or not. - * @var boolean - */ - var $_https; - /** * Flag whether to verify HTTPS server certificates or not. * @var boolean @@ -98,24 +92,18 @@ class Auth_Yubico * Sets up the object * @param string $id The client identity * @param string $key The client MAC key (optional) - * @param boolean $https Flag whether to use https (optional) + * @param boolean $https noop * @param boolean $httpsverify Flag whether to use verify HTTPS * server certificates (optional, * default true) * @access public */ - function __construct($id, $key = '', $https = 0, $httpsverify = 1) + public function __construct($id, $key = '', $https = 0, $httpsverify = 1) { $this->_id = $id; $this->_key = base64_decode($key); - $this->_https = $https; $this->_httpsverify = $httpsverify; } - - function Auth_Yubico($id, $key = '', $https = 0, $httpsverify = 1) - { - self::__construct(); - } /** * Specify to use a different URL part for verification. @@ -129,22 +117,6 @@ class Auth_Yubico $this->_url = $url; } - /** - * Get URL part to use for validation. - * - * @return string Server URL part - * @access public - */ - function getURLpart() - { - if ($this->_url) { - return $this->_url; - } else { - return "api.yubico.com/wsapi/verify"; - } - } - - /** * Get next URL part from list to use for validation. * @@ -154,12 +126,12 @@ class Auth_Yubico function getNextURLpart() { if ($this->_url_list) $url_list=$this->_url_list; - else $url_list=array('api.yubico.com/wsapi/2.0/verify', - 'api2.yubico.com/wsapi/2.0/verify', - 'api3.yubico.com/wsapi/2.0/verify', - 'api4.yubico.com/wsapi/2.0/verify', - 'api5.yubico.com/wsapi/2.0/verify'); - + else $url_list=array('https://api.yubico.com/wsapi/2.0/verify', + 'https://api2.yubico.com/wsapi/2.0/verify', + 'https://api3.yubico.com/wsapi/2.0/verify', + 'https://api4.yubico.com/wsapi/2.0/verify', + 'https://api5.yubico.com/wsapi/2.0/verify'); + if ($this->_url_index>=count($url_list)) return false; else return $url_list[$this->_url_index++]; } @@ -318,13 +290,7 @@ class Auth_Yubico $ch = array(); while($URLpart=$this->getNextURLpart()) { - /* Support https. */ - if ($this->_https) { - $query = "https://"; - } else { - $query = "http://"; - } - $query .= $URLpart . "?" . $parameters; + $query = $URLpart . "?" . $parameters; if ($this->_lastquery) { $this->_lastquery .= " "; } $this->_lastquery .= $query; @@ -392,7 +358,7 @@ class Auth_Yubico /* Case 2. Verify signature first */ $rows = explode("\r\n", trim($str)); $response=array(); - while (list($key, $val) = each($rows)) { + foreach ($rows as $key => $val) { /* = is also used in BASE64 encoding so we only replace the first = by # which is not used in BASE64 */ $val = preg_replace('/=/', '#', $val, 1); $row = explode("#", $val); diff --git a/data/web/inc/lib/composer.json b/data/web/inc/lib/composer.json index 6107bac7..e072a0dd 100644 --- a/data/web/inc/lib/composer.json +++ b/data/web/inc/lib/composer.json @@ -4,6 +4,8 @@ "yubico/u2flib-server": "^1.0", "phpmailer/phpmailer": "^5.2", "php-mime-mail-parser/php-mime-mail-parser": "^2.9", - "soundasleep/html2text": "^0.5.0" + "soundasleep/html2text": "^0.5.0", + "ddeboer/imap": "^1.5", + "matthiasmullie/minify": "^1.3" } } diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock index 841f2e93..b2d6defe 100644 --- a/data/web/inc/lib/composer.lock +++ b/data/web/inc/lib/composer.lock @@ -4,8 +4,221 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3edeec2e3fa875d4f9d5e7f22a8179be", + "content-hash": "e72f119b7f62fea0aa6123109abb9a35", "packages": [ + { + "name": "ddeboer/imap", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/ddeboer/imap.git", + "reference": "4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede", + "reference": "4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*", + "php": "^7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.13", + "phpstan/phpstan": "^0.9.1", + "phpstan/phpstan-phpunit": "^0.9.3", + "phpunit/phpunit": "^7.4", + "zendframework/zend-mail": "^2.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" + } + ], + "description": "Object-oriented IMAP for PHP", + "keywords": [ + "email", + "imap", + "mail" + ], + "time": "2018-12-04T13:35:19+00:00" + }, + { + "name": "matthiasmullie/minify", + "version": "1.3.61", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/minify.git", + "reference": "d5acb8ce5b6acb7d11bafe97cecc533f6e4fd751" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/d5acb8ce5b6acb7d11bafe97cecc533f6e4fd751", + "reference": "d5acb8ce5b6acb7d11bafe97cecc533f6e4fd751", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.0", + "matthiasmullie/scrapbook": "~1.0", + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ], + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "minify@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "homepage": "http://www.minifier.org", + "keywords": [ + "JS", + "css", + "javascript", + "minifier", + "minify" + ], + "time": "2018-11-26T23:10:39+00:00" + }, + { + "name": "matthiasmullie/path-converter", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/path-converter.git", + "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/5e4b121c8b9f97c80835c1d878b0812ba1d607c9", + "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "pathconverter@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Relative path converter", + "homepage": "http://github.com/matthiasmullie/path-converter", + "keywords": [ + "converter", + "path", + "paths", + "relative" + ], + "time": "2018-10-25T15:19:41+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, { "name": "php-mime-mail-parser/php-mime-mail-parser", "version": "2.11.1", @@ -88,16 +301,16 @@ }, { "name": "phpmailer/phpmailer", - "version": "v5.2.26", + "version": "v5.2.27", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "70362997bda4376378be7d92d81e2200550923f7" + "reference": "dde1db116511aa4956389d75546c5be4c2beb2a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7", - "reference": "70362997bda4376378be7d92d81e2200550923f7", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/dde1db116511aa4956389d75546c5be4c2beb2a6", + "reference": "dde1db116511aa4956389d75546c5be4c2beb2a6", "shasum": "" }, "require": { @@ -161,7 +374,7 @@ } ], "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "time": "2017-11-04T09:26:05+00:00" + "time": "2018-11-15T22:32:31+00:00" }, { "name": "robthree/twofactorauth", @@ -266,24 +479,26 @@ }, { "name": "yubico/u2flib-server", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/Yubico/php-u2flib-server.git", - "reference": "dc318c80b59e62921c210f31b014def26ceebbab" + "reference": "55d813acf68212ad2cadecde07551600d6971939" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/dc318c80b59e62921c210f31b014def26ceebbab", - "reference": "dc318c80b59e62921c210f31b014def26ceebbab", + "url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/55d813acf68212ad2cadecde07551600d6971939", + "reference": "55d813acf68212ad2cadecde07551600d6971939", "shasum": "" }, "require": { "ext-openssl": "*", + "paragonie/random_compat": ">= 1", "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "~5.7" + "phpunit/phpunit": "~5.7", + "vimeo/psalm": "^0|^1|^2" }, "type": "library", "autoload": { @@ -297,7 +512,7 @@ ], "description": "Library for U2F implementation", "homepage": "https://developers.yubico.com/php-u2flib-server", - "time": "2017-05-09T07:33:58+00:00" + "time": "2018-09-07T08:16:44+00:00" } ], "packages-dev": [], diff --git a/data/web/inc/lib/vendor/bin/minifycss b/data/web/inc/lib/vendor/bin/minifycss new file mode 120000 index 00000000..04f60a4b --- /dev/null +++ b/data/web/inc/lib/vendor/bin/minifycss @@ -0,0 +1 @@ +../matthiasmullie/minify/bin/minifycss \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bin/minifyjs b/data/web/inc/lib/vendor/bin/minifyjs new file mode 120000 index 00000000..61124467 --- /dev/null +++ b/data/web/inc/lib/vendor/bin/minifyjs @@ -0,0 +1 @@ +../matthiasmullie/minify/bin/minifyjs \ No newline at end of file diff --git a/data/web/inc/lib/vendor/composer/ClassLoader.php b/data/web/inc/lib/vendor/composer/ClassLoader.php index dc02dfb1..fce8549f 100644 --- a/data/web/inc/lib/vendor/composer/ClassLoader.php +++ b/data/web/inc/lib/vendor/composer/ClassLoader.php @@ -279,7 +279,7 @@ class ClassLoader */ public function setApcuPrefix($apcuPrefix) { - $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** @@ -377,7 +377,7 @@ class ClassLoader $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); - $search = $subPath.'\\'; + $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { diff --git a/data/web/inc/lib/vendor/composer/autoload_psr4.php b/data/web/inc/lib/vendor/composer/autoload_psr4.php index cfa01d56..e7b93543 100644 --- a/data/web/inc/lib/vendor/composer/autoload_psr4.php +++ b/data/web/inc/lib/vendor/composer/autoload_psr4.php @@ -8,5 +8,8 @@ $baseDir = dirname($vendorDir); return array( 'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'), 'PhpMimeMailParser\\' => array($vendorDir . '/php-mime-mail-parser/php-mime-mail-parser/src'), + 'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'), + 'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'), 'Html2Text\\' => array($vendorDir . '/soundasleep/html2text/src'), + 'Ddeboer\\Imap\\' => array($vendorDir . '/ddeboer/imap/src'), ); diff --git a/data/web/inc/lib/vendor/composer/autoload_static.php b/data/web/inc/lib/vendor/composer/autoload_static.php index 9d9f1a85..7bcc3ed6 100644 --- a/data/web/inc/lib/vendor/composer/autoload_static.php +++ b/data/web/inc/lib/vendor/composer/autoload_static.php @@ -15,10 +15,19 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b array ( 'PhpMimeMailParser\\' => 18, ), + 'M' => + array ( + 'MatthiasMullie\\PathConverter\\' => 29, + 'MatthiasMullie\\Minify\\' => 22, + ), 'H' => array ( 'Html2Text\\' => 10, ), + 'D' => + array ( + 'Ddeboer\\Imap\\' => 13, + ), ); public static $prefixDirsPsr4 = array ( @@ -30,10 +39,22 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b array ( 0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src', ), + 'MatthiasMullie\\PathConverter\\' => + array ( + 0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src', + ), + 'MatthiasMullie\\Minify\\' => + array ( + 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src', + ), 'Html2Text\\' => array ( 0 => __DIR__ . '/..' . '/soundasleep/html2text/src', ), + 'Ddeboer\\Imap\\' => + array ( + 0 => __DIR__ . '/..' . '/ddeboer/imap/src', + ), ); public static $classMap = array ( diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json index bd1deb85..42f210d0 100644 --- a/data/web/inc/lib/vendor/composer/installed.json +++ b/data/web/inc/lib/vendor/composer/installed.json @@ -1,4 +1,225 @@ [ + { + "name": "ddeboer/imap", + "version": "1.6.0", + "version_normalized": "1.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/ddeboer/imap.git", + "reference": "4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede", + "reference": "4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*", + "php": "^7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.13", + "phpstan/phpstan": "^0.9.1", + "phpstan/phpstan-phpunit": "^0.9.3", + "phpunit/phpunit": "^7.4", + "zendframework/zend-mail": "^2.10" + }, + "time": "2018-12-04T13:35:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" + } + ], + "description": "Object-oriented IMAP for PHP", + "keywords": [ + "email", + "imap", + "mail" + ] + }, + { + "name": "matthiasmullie/minify", + "version": "1.3.61", + "version_normalized": "1.3.61.0", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/minify.git", + "reference": "d5acb8ce5b6acb7d11bafe97cecc533f6e4fd751" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/d5acb8ce5b6acb7d11bafe97cecc533f6e4fd751", + "reference": "d5acb8ce5b6acb7d11bafe97cecc533f6e4fd751", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.0", + "matthiasmullie/scrapbook": "~1.0", + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "time": "2018-11-26T23:10:39+00:00", + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "minify@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "homepage": "http://www.minifier.org", + "keywords": [ + "JS", + "css", + "javascript", + "minifier", + "minify" + ] + }, + { + "name": "matthiasmullie/path-converter", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/path-converter.git", + "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/5e4b121c8b9f97c80835c1d878b0812ba1d607c9", + "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "time": "2018-10-25T15:19:41+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "pathconverter@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Relative path converter", + "homepage": "http://github.com/matthiasmullie/path-converter", + "keywords": [ + "converter", + "path", + "paths", + "relative" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "version_normalized": "9.99.99.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2018-07-02T15:55:56+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ] + }, { "name": "php-mime-mail-parser/php-mime-mail-parser", "version": "2.11.1", @@ -83,17 +304,17 @@ }, { "name": "phpmailer/phpmailer", - "version": "v5.2.26", - "version_normalized": "5.2.26.0", + "version": "v5.2.27", + "version_normalized": "5.2.27.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "70362997bda4376378be7d92d81e2200550923f7" + "reference": "dde1db116511aa4956389d75546c5be4c2beb2a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7", - "reference": "70362997bda4376378be7d92d81e2200550923f7", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/dde1db116511aa4956389d75546c5be4c2beb2a6", + "reference": "dde1db116511aa4956389d75546c5be4c2beb2a6", "shasum": "" }, "require": { @@ -123,7 +344,7 @@ "suggest": { "league/oauth2-google": "Needed for Google XOAUTH2 authentication" }, - "time": "2017-11-04T09:26:05+00:00", + "time": "2018-11-15T22:32:31+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -267,27 +488,29 @@ }, { "name": "yubico/u2flib-server", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "version": "1.0.2", + "version_normalized": "1.0.2.0", "source": { "type": "git", "url": "https://github.com/Yubico/php-u2flib-server.git", - "reference": "dc318c80b59e62921c210f31b014def26ceebbab" + "reference": "55d813acf68212ad2cadecde07551600d6971939" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/dc318c80b59e62921c210f31b014def26ceebbab", - "reference": "dc318c80b59e62921c210f31b014def26ceebbab", + "url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/55d813acf68212ad2cadecde07551600d6971939", + "reference": "55d813acf68212ad2cadecde07551600d6971939", "shasum": "" }, "require": { "ext-openssl": "*", + "paragonie/random_compat": ">= 1", "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "~5.7" + "phpunit/phpunit": "~5.7", + "vimeo/psalm": "^0|^1|^2" }, - "time": "2017-05-09T07:33:58+00:00", + "time": "2018-09-07T08:16:44+00:00", "type": "library", "installation-source": "dist", "autoload": { diff --git a/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md b/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md new file mode 100644 index 00000000..4635313f --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md @@ -0,0 +1,510 @@ +# Change Log + +## [1.6.0](https://github.com/ddeboer/imap/tree/1.6.0) (2018-12-04) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.5...1.6.0) + +**Implemented enhancements:** + +- Require PHP ^7.1 [\#257](https://github.com/ddeboer/imap/issues/257) +- Require PHP ^7.1 [\#383](https://github.com/ddeboer/imap/pull/383) ([Slamdunk](https://github.com/Slamdunk)) +- Add ability to pass options and retries to imap\_open [\#382](https://github.com/ddeboer/imap/pull/382) ([Slamdunk](https://github.com/Slamdunk)) +- Docker setup for running tests [\#374](https://github.com/ddeboer/imap/pull/374) ([LeadTechVisas](https://github.com/LeadTechVisas)) +- Get messages by UID sequence [\#373](https://github.com/ddeboer/imap/pull/373) ([LeadTechVisas](https://github.com/LeadTechVisas)) + +**Fixed bugs:** + +- Undeliverable mail: attachment parsing error [\#334](https://github.com/ddeboer/imap/issues/334) +- imap\_getmailboxes returns false; [\#134](https://github.com/ddeboer/imap/issues/134) +- Fix mailbox name as only numbers [\#381](https://github.com/ddeboer/imap/pull/381) ([Slamdunk](https://github.com/Slamdunk)) +- Gracefully handle possible non-array return value of imap\_getmailboxes [\#372](https://github.com/ddeboer/imap/pull/372) ([Slamdunk](https://github.com/Slamdunk)) + +**Closed issues:** + +- \[AUTHENTICATIONFAILED\] Authentication failed - Too many login failures [\#368](https://github.com/ddeboer/imap/issues/368) +- last folder in list [\#353](https://github.com/ddeboer/imap/issues/353) +- Caching IMAP server connections [\#88](https://github.com/ddeboer/imap/issues/88) + +## [1.5.5](https://github.com/ddeboer/imap/tree/1.5.5) (2018-08-21) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.4...1.5.5) + +**Fixed bugs:** + +- Plain text attachments are not identified as Attachment parts [\#341](https://github.com/ddeboer/imap/issues/341) +- Handle plain/text attachments without Content-Type header [\#367](https://github.com/ddeboer/imap/pull/367) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.4](https://github.com/ddeboer/imap/tree/1.5.4) (2018-08-19) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.3...1.5.4) + +**Fixed bugs:** + +- Very long filename, result of getFilename\(\) = NULL? [\#365](https://github.com/ddeboer/imap/issues/365) +- Support RFC2231 attachment filenames [\#366](https://github.com/ddeboer/imap/pull/366) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.3](https://github.com/ddeboer/imap/tree/1.5.3) (2018-07-20) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.2...1.5.3) + +**Fixed bugs:** + +- Dates: handle UT timezone [\#361](https://github.com/ddeboer/imap/pull/361) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.2](https://github.com/ddeboer/imap/tree/1.5.2) (2018-07-10) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.1...1.5.2) + +**Fixed bugs:** + +- Fails to load Message Headers [\#358](https://github.com/ddeboer/imap/issues/358) +- Handle invalid headers [\#359](https://github.com/ddeboer/imap/pull/359) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.1](https://github.com/ddeboer/imap/tree/1.5.1) (2018-05-04) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.0...1.5.1) + +**Fixed bugs:** + +- getContent\(\) method returns wrong content part [\#342](https://github.com/ddeboer/imap/issues/342) +- Fix handle of attachment messages with attachments [\#343](https://github.com/ddeboer/imap/pull/343) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.0](https://github.com/ddeboer/imap/tree/1.5.0) (2018-03-26) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.4.1...1.5.0) + +**Implemented enhancements:** + +- ImapResource: cache last opened mailbox [\#328](https://github.com/ddeboer/imap/pull/328) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.4.1](https://github.com/ddeboer/imap/tree/1.4.1) (2018-03-22) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.4.0...1.4.1) + +**Fixed bugs:** + +- Return value of Ddeboer\\Imap\\Message\\AbstractPart::getDecodedContent\(\) must be of the type string, boolean returned [\#284](https://github.com/ddeboer/imap/issues/284) +- base64\_decode may return false in PHP \< 7.1 [\#324](https://github.com/ddeboer/imap/pull/324) ([Slamdunk](https://github.com/Slamdunk)) + +**Merged pull requests:** + +- Add entry in README about Mailbox::addMessage [\#325](https://github.com/ddeboer/imap/pull/325) ([soywod](https://github.com/soywod)) + +## [1.4.0](https://github.com/ddeboer/imap/tree/1.4.0) (2018-03-19) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.3.1...1.4.0) + +**Implemented enhancements:** + +- Lazy load Message [\#320](https://github.com/ddeboer/imap/pull/320) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Invalid argument supplied for foreach\(\) in Parameters.php line 52 [\#317](https://github.com/ddeboer/imap/issues/317) +- Message "11964" does not exist: imap\_fetchstructure\(\): Bad message number [\#310](https://github.com/ddeboer/imap/issues/310) +- imap\_mime\_header\_decode may return false [\#322](https://github.com/ddeboer/imap/pull/322) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.3.1](https://github.com/ddeboer/imap/tree/1.3.1) (2018-03-09) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.3.0...1.3.1) + +**Implemented enhancements:** + +- Allow empty port [\#312](https://github.com/ddeboer/imap/pull/312) ([Slamdunk](https://github.com/Slamdunk)) + +**Closed issues:** + +- getServerString\(\) with no port [\#311](https://github.com/ddeboer/imap/issues/311) + +## [1.3.0](https://github.com/ddeboer/imap/tree/1.3.0) (2018-02-28) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.2.3...1.3.0) + +**Implemented enhancements:** + +- Implement bulk-move [\#306](https://github.com/ddeboer/imap/pull/306) ([particleflux](https://github.com/particleflux)) + +**Closed issues:** + +- feature: Bulk move [\#305](https://github.com/ddeboer/imap/issues/305) + +**Merged pull requests:** + +- README.md: add `Unknown search criterion: OR` note [\#304](https://github.com/ddeboer/imap/pull/304) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.2.3](https://github.com/ddeboer/imap/tree/1.2.3) (2018-02-09) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.2.2...1.2.3) + +**Fixed bugs:** + +- $part-\>type can be 9 [\#301](https://github.com/ddeboer/imap/issues/301) +- AbstractPart::isAttachment\(\) handle unknown part type [\#302](https://github.com/ddeboer/imap/pull/302) ([Slamdunk](https://github.com/Slamdunk)) + +**Merged pull requests:** + +- README.md: code-coverage has higher priority than Scrutinizer [\#300](https://github.com/ddeboer/imap/pull/300) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.2.2](https://github.com/ddeboer/imap/tree/1.2.2) (2018-02-05) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.2.1...1.2.2) + +**Implemented enhancements:** + +- Allow PHPUnit ^7.0 [\#296](https://github.com/ddeboer/imap/pull/296) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Attachment-\>getFilename return null [\#297](https://github.com/ddeboer/imap/issues/297) +- Don't handle multiplart as an attachment [\#298](https://github.com/ddeboer/imap/pull/298) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.2.1](https://github.com/ddeboer/imap/tree/1.2.1) (2018-01-29) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.2.0...1.2.1) + +**Implemented enhancements:** + +- Introduce strict comparison [\#289](https://github.com/ddeboer/imap/pull/289) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Invalid Date header found: "Thur, 04 Jan 2018 06:44:23 +0400" [\#293](https://github.com/ddeboer/imap/issues/293) +- MessageIterator::current\(\) fails when there are no messages [\#288](https://github.com/ddeboer/imap/issues/288) +- Remove weekday while parsing date header [\#294](https://github.com/ddeboer/imap/pull/294) ([Slamdunk](https://github.com/Slamdunk)) +- MessageIterator: forbid raw calls [\#290](https://github.com/ddeboer/imap/pull/290) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.2.0](https://github.com/ddeboer/imap/tree/1.2.0) (2018-01-15) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.1.2...1.2.0) + +**Implemented enhancements:** + +- Make imap\_append\(\) optional arguments reachable [\#280](https://github.com/ddeboer/imap/pull/280) ([Slamdunk](https://github.com/Slamdunk)) +- PHPStan: introduce static analysis [\#276](https://github.com/ddeboer/imap/pull/276) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- getAttachments\(\) problem when mixin inline and attachment [\#281](https://github.com/ddeboer/imap/issues/281) +- UnexpectedEncodingException: Cannot decode "5" [\#278](https://github.com/ddeboer/imap/issues/278) +- Handle correctly multiple nested attachments [\#283](https://github.com/ddeboer/imap/pull/283) ([Slamdunk](https://github.com/Slamdunk)) +- Manageable UnexpectedEncodingException [\#282](https://github.com/ddeboer/imap/pull/282) ([Slamdunk](https://github.com/Slamdunk)) + +**Closed issues:** + +- Appending mail with options [\#279](https://github.com/ddeboer/imap/issues/279) + +## [1.1.2](https://github.com/ddeboer/imap/tree/1.1.2) (2017-12-12) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.1.1...1.1.2) + +**Fixed bugs:** + +- Unsupported charset "134": mb\_convert\_encoding\(\): Illegal character encoding specified [\#270](https://github.com/ddeboer/imap/issues/270) +- Support Microsoft charset values [\#271](https://github.com/ddeboer/imap/pull/271) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.1.1](https://github.com/ddeboer/imap/tree/1.1.1) (2017-11-10) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.1.0...1.1.1) + +**Implemented enhancements:** + +- Transcoder: expand charset aliases list [\#267](https://github.com/ddeboer/imap/pull/267) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Charset aliases: fix to lowercase search [\#266](https://github.com/ddeboer/imap/pull/266) ([Slamdunk](https://github.com/Slamdunk)) + +**Merged pull requests:** + +- README.md: add timeout note [\#263](https://github.com/ddeboer/imap/pull/263) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.1.0](https://github.com/ddeboer/imap/tree/1.1.0) (2017-11-06) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.8...1.1.0) + +**Implemented enhancements:** + +- Headers: no catchable exception [\#246](https://github.com/ddeboer/imap/issues/246) +- imap\_thread [\#113](https://github.com/ddeboer/imap/issues/113) +- Deprecate MessageInterface::maskAsSeen\(\) in favour of MessageInterface::markAsSeen\(\) [\#255](https://github.com/ddeboer/imap/pull/255) ([Slamdunk](https://github.com/Slamdunk)) +- Lazy load structured Headers [\#250](https://github.com/ddeboer/imap/pull/250) ([Slamdunk](https://github.com/Slamdunk)) +- Implement imap\_thread [\#249](https://github.com/ddeboer/imap/pull/249) ([Slamdunk](https://github.com/Slamdunk)) +- Require ext-iconv [\#248](https://github.com/ddeboer/imap/pull/248) ([Slamdunk](https://github.com/Slamdunk)) +- Message Part: expose $partNumber [\#244](https://github.com/ddeboer/imap/pull/244) ([wujku](https://github.com/wujku)) +- Add Mockability helpers and documentation [\#236](https://github.com/ddeboer/imap/pull/236) ([Slamdunk](https://github.com/Slamdunk)) +- Add missing interface change for \#225 [\#233](https://github.com/ddeboer/imap/pull/233) ([Slamdunk](https://github.com/Slamdunk)) +- Connection: check if the connection is still active with `imap\_ping` [\#232](https://github.com/ddeboer/imap/pull/232) ([wujku](https://github.com/wujku)) +- Message: add `References` and `In-Reply-To` headers shortcuts [\#230](https://github.com/ddeboer/imap/pull/230) ([wujku](https://github.com/wujku)) +- Added bulk set / clear flags functionality for mailbox messages [\#225](https://github.com/ddeboer/imap/pull/225) ([wujku](https://github.com/wujku)) + +**Merged pull requests:** + +- make docs more obvious [\#252](https://github.com/ddeboer/imap/pull/252) ([lgg](https://github.com/lgg)) +- README.md: add Table of Contents with Travis checker [\#234](https://github.com/ddeboer/imap/pull/234) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.8](https://github.com/ddeboer/imap/tree/1.0.8) (2017-10-27) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.7...1.0.8) + +**Fixed bugs:** + +- \[TypeError\] Return value of Ddeboer\Imap\Message\AbstractMessage::getId\(\) must be of the type string, null returned [\#253](https://github.com/ddeboer/imap/issues/253) +- BasicMessageInterface::getId\(\) can be null [\#254](https://github.com/ddeboer/imap/pull/254) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.7](https://github.com/ddeboer/imap/tree/1.0.7) (2017-10-16) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.6...1.0.7) + +**Fixed bugs:** + +- Problem with a IMAP resource stream [\#245](https://github.com/ddeboer/imap/issues/245) +- IMAP resource must be checked at every call for mailbox context [\#247](https://github.com/ddeboer/imap/pull/247) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.6](https://github.com/ddeboer/imap/tree/1.0.6) (2017-10-12) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.5...1.0.6) + +**Fixed bugs:** + +- \[TypeError\] Return value of AbstractMessage::getFrom\(\) must be an instance of EmailAddress, null returned [\#241](https://github.com/ddeboer/imap/issues/241) +- Message: Date header can be absent [\#243](https://github.com/ddeboer/imap/pull/243) ([Slamdunk](https://github.com/Slamdunk)) +- Message: From header can be absent [\#242](https://github.com/ddeboer/imap/pull/242) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.5](https://github.com/ddeboer/imap/tree/1.0.5) (2017-10-12) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.4...1.0.5) + +**Fixed bugs:** + +- Use set\_error\_handler with late exception [\#240](https://github.com/ddeboer/imap/pull/240) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.4](https://github.com/ddeboer/imap/tree/1.0.4) (2017-10-11) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.3...1.0.4) + +**Implemented enhancements:** + +- Avoid \(set|restor\)\_error\_handler [\#239](https://github.com/ddeboer/imap/pull/239) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Current Transcoder class does not support all charsets. [\#237](https://github.com/ddeboer/imap/issues/237) +- Relay also iconv during decoding [\#238](https://github.com/ddeboer/imap/pull/238) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.3](https://github.com/ddeboer/imap/tree/1.0.3) (2017-10-11) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.2...1.0.3) + +**Fixed bugs:** + +- Attachment::getFilename\(\) may be null on inline-att, widen return type [\#235](https://github.com/ddeboer/imap/pull/235) ([wujku](https://github.com/wujku)) + +## [1.0.2](https://github.com/ddeboer/imap/tree/1.0.2) (2017-10-06) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.1...1.0.2) + +**Fixed bugs:** + +- Issue with saving XML attachments [\#228](https://github.com/ddeboer/imap/issues/228) +- Do not charset-decode attachments [\#231](https://github.com/ddeboer/imap/pull/231) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.1](https://github.com/ddeboer/imap/tree/1.0.1) (2017-10-05) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.0...1.0.1) + +**Fixed bugs:** + +- Error with attachment charset [\#226](https://github.com/ddeboer/imap/issues/226) +- If charset is not specified defaults to "us-ascii" [\#227](https://github.com/ddeboer/imap/pull/227) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.0](https://github.com/ddeboer/imap/tree/1.0.0) (2017-10-04) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.5.2...1.0.0) + +**Implemented enhancements:** + +- Need getAll for headers [\#200](https://github.com/ddeboer/imap/issues/200) +- Tests: implement @covers to avoid false positive on code-coverage [\#188](https://github.com/ddeboer/imap/issues/188) +- Remove commented code [\#174](https://github.com/ddeboer/imap/issues/174) +- Regex in SearchExpressions [\#157](https://github.com/ddeboer/imap/issues/157) +- How do I get unread messages count? [\#98](https://github.com/ddeboer/imap/issues/98) +- Add mocking ability through Interfaces [\#221](https://github.com/ddeboer/imap/pull/221) ([Slamdunk](https://github.com/Slamdunk)) +- Wrap imap resource to periodically check its status [\#220](https://github.com/ddeboer/imap/pull/220) ([Slamdunk](https://github.com/Slamdunk)) +- Add more coding-standard rules [\#218](https://github.com/ddeboer/imap/pull/218) ([Slamdunk](https://github.com/Slamdunk)) +- Always keep unseen: remove keepUnseen, add markAsSeen [\#217](https://github.com/ddeboer/imap/pull/217) ([Slamdunk](https://github.com/Slamdunk)) +- Embedded messages: refactor \#106 [\#216](https://github.com/ddeboer/imap/pull/216) ([Slamdunk](https://github.com/Slamdunk)) +- Headers now extends \ArrayIterator [\#215](https://github.com/ddeboer/imap/pull/215) ([Slamdunk](https://github.com/Slamdunk)) +- Implement imap\_mail\_copy [\#214](https://github.com/ddeboer/imap/pull/214) ([Slamdunk](https://github.com/Slamdunk)) +- Imap sort [\#213](https://github.com/ddeboer/imap/pull/213) ([Slamdunk](https://github.com/Slamdunk)) +- Increased code-coverage [\#211](https://github.com/ddeboer/imap/pull/211) ([Slamdunk](https://github.com/Slamdunk)) +- Update to PHPUnit ^6.2 [\#209](https://github.com/ddeboer/imap/pull/209) ([Slamdunk](https://github.com/Slamdunk)) +- Use specific exceptions to ease user catches [\#208](https://github.com/ddeboer/imap/pull/208) ([Slamdunk](https://github.com/Slamdunk)) +- Wrap Exception on invalid Date header [\#205](https://github.com/ddeboer/imap/pull/205) ([Slamdunk](https://github.com/Slamdunk)) +- Add tests for \#144 set flags functionalities [\#203](https://github.com/ddeboer/imap/pull/203) ([Slamdunk](https://github.com/Slamdunk)) +- Add imap\_fetchheader\(\) functionality to get raw headers [\#202](https://github.com/ddeboer/imap/pull/202) ([Slamdunk](https://github.com/Slamdunk)) +- Parse all email type headers [\#199](https://github.com/ddeboer/imap/pull/199) ([Slamdunk](https://github.com/Slamdunk)) +- Test search conditions [\#198](https://github.com/ddeboer/imap/pull/198) ([Slamdunk](https://github.com/Slamdunk)) +- Mailbox: get status [\#192](https://github.com/ddeboer/imap/pull/192) ([Slamdunk](https://github.com/Slamdunk)) +- SearchExpression is a Search\ConditionInterface [\#191](https://github.com/ddeboer/imap/pull/191) ([Slamdunk](https://github.com/Slamdunk)) +- SearchCondition: \_\_toString\(\) -\> toString\(\) [\#187](https://github.com/ddeboer/imap/pull/187) ([Slamdunk](https://github.com/Slamdunk)) +- Retain imap\_getmailboxes\(\) results [\#184](https://github.com/ddeboer/imap/pull/184) ([Slamdunk](https://github.com/Slamdunk)) +- Add type hints and return types [\#183](https://github.com/ddeboer/imap/pull/183) ([Slamdunk](https://github.com/Slamdunk)) +- Exception: increase verbosity with imap\_alerts\(\) and imap\_errors\(\) [\#182](https://github.com/ddeboer/imap/pull/182) ([Slamdunk](https://github.com/Slamdunk)) +- Add coding-standards [\#181](https://github.com/ddeboer/imap/pull/181) ([Slamdunk](https://github.com/Slamdunk)) +- Travis: re-enable code-coverage on scrutinizer [\#177](https://github.com/ddeboer/imap/pull/177) ([Slamdunk](https://github.com/Slamdunk)) +- Add .gitattributes to remove from releases unneded files [\#173](https://github.com/ddeboer/imap/pull/173) ([Slamdunk](https://github.com/Slamdunk)) +- Travis: use local Dovecot installation [\#170](https://github.com/ddeboer/imap/pull/170) ([Slamdunk](https://github.com/Slamdunk)) +- Need all Headers in string format [\#149](https://github.com/ddeboer/imap/pull/149) ([FlashWS](https://github.com/FlashWS)) +- Get raw mail [\#146](https://github.com/ddeboer/imap/pull/146) ([styxit](https://github.com/styxit)) +- add getBcc\(\), Set, Clear Flag\(\Seen, \Answered, \Flagged, \Deleted, and \Draft\), getHeadersRaw\(\) [\#144](https://github.com/ddeboer/imap/pull/144) ([trungpv93](https://github.com/trungpv93)) + +**Fixed bugs:** + +- Search\Condition needs charset escaping/indication [\#190](https://github.com/ddeboer/imap/issues/190) +- imap\_utf7\_\(encode|decode\) -\> mb\_convert\_encoding [\#185](https://github.com/ddeboer/imap/issues/185) +- Espaรฑa [\#176](https://github.com/ddeboer/imap/issues/176) +- getHeaders\(\) decode broke information [\#171](https://github.com/ddeboer/imap/issues/171) +- Date format for date search condition [\#168](https://github.com/ddeboer/imap/issues/168) +- Error when trying fetch messages from container [\#167](https://github.com/ddeboer/imap/issues/167) +- Attachment encoding error [\#158](https://github.com/ddeboer/imap/issues/158) +- getFilename\(\) is empty and no attachment, even when there is an attachment. [\#142](https://github.com/ddeboer/imap/issues/142) +- Encoding issues [\#136](https://github.com/ddeboer/imap/issues/136) +- URGENT: The timezone could not be found in the database [\#135](https://github.com/ddeboer/imap/issues/135) +- Incorrect transcoding of text attachments [\#132](https://github.com/ddeboer/imap/issues/132) +- Undefined offset [\#123](https://github.com/ddeboer/imap/issues/123) +- ICS file not supported as attachment [\#120](https://github.com/ddeboer/imap/issues/120) +- Should iconv be a requirement? [\#115](https://github.com/ddeboer/imap/issues/115) +- KeepUnseen doen't work [\#92](https://github.com/ddeboer/imap/issues/92) +- PHP Fatal error Failed to parse time string in ddeboer/imap/src/Message.php [\#89](https://github.com/ddeboer/imap/issues/89) +- encoding issue [\#85](https://github.com/ddeboer/imap/issues/85) +- keepUnseen not working correctly with Hotmail [\#84](https://github.com/ddeboer/imap/issues/84) +- Iconv Exception [\#78](https://github.com/ddeboer/imap/issues/78) +- $message-\>getAttachments\(\) doesn't recognize some attachments [\#74](https://github.com/ddeboer/imap/issues/74) +- Message::move\(\) doesn't work. [\#73](https://github.com/ddeboer/imap/issues/73) +- Message\Part: part number must distinguish original message [\#223](https://github.com/ddeboer/imap/pull/223) ([Slamdunk](https://github.com/Slamdunk)) +- Recursive Embedded email body bug [\#222](https://github.com/ddeboer/imap/pull/222) ([Slamdunk](https://github.com/Slamdunk)) +- Exclude HTML from allowed attachment subtype [\#212](https://github.com/ddeboer/imap/pull/212) ([Slamdunk](https://github.com/Slamdunk)) +- Fix imap\_mail\_move behaviour and test it [\#207](https://github.com/ddeboer/imap/pull/207) ([Slamdunk](https://github.com/Slamdunk)) +- Undefined encoding: throw exception [\#197](https://github.com/ddeboer/imap/pull/197) ([Slamdunk](https://github.com/Slamdunk)) +- Message charset: mb\_convert\_encoding + aliases [\#196](https://github.com/ddeboer/imap/pull/196) ([Slamdunk](https://github.com/Slamdunk)) +- Mailbox: only UTF-8 names [\#193](https://github.com/ddeboer/imap/pull/193) ([Slamdunk](https://github.com/Slamdunk)) +- Search\Date\AbstractDate: fix format to RFC-3501 [\#189](https://github.com/ddeboer/imap/pull/189) ([Slamdunk](https://github.com/Slamdunk)) +- Travis: fix failing tests [\#172](https://github.com/ddeboer/imap/pull/172) ([Slamdunk](https://github.com/Slamdunk)) +- Return body of single-part HTML message as HTML, not text [\#101](https://github.com/ddeboer/imap/pull/101) ([joker806](https://github.com/joker806)) +- Implement "undisclosed recipients" addresses [\#86](https://github.com/ddeboer/imap/pull/86) ([darit](https://github.com/darit)) + +**Closed issues:** + +- Potential memory issue with attachments [\#195](https://github.com/ddeboer/imap/issues/195) +- Explain Message::delete [\#175](https://github.com/ddeboer/imap/issues/175) +- Get raw message [\#161](https://github.com/ddeboer/imap/issues/161) +- Composer install problem [\#160](https://github.com/ddeboer/imap/issues/160) +- Transcoder not exist [\#154](https://github.com/ddeboer/imap/issues/154) +- The library doesn't support using sort by [\#151](https://github.com/ddeboer/imap/issues/151) +- Office 365 - Array to string conversion error [\#131](https://github.com/ddeboer/imap/issues/131) +- Is there a method to turn a seen message into an "unseen" one ? [\#130](https://github.com/ddeboer/imap/issues/130) +- Create mailbox [\#126](https://github.com/ddeboer/imap/issues/126) +- Move and Delete Message not working [\#112](https://github.com/ddeboer/imap/issues/112) +- Problem on production server [\#111](https://github.com/ddeboer/imap/issues/111) +- Authentication failed for a Gmail account [\#109](https://github.com/ddeboer/imap/issues/109) +- A method to run IMAP commands? [\#83](https://github.com/ddeboer/imap/issues/83) + +**Merged pull requests:** + +- Update README.md to latest develop changes [\#224](https://github.com/ddeboer/imap/pull/224) ([Slamdunk](https://github.com/Slamdunk)) +- Add Filippo Tessarotto as an author of the package [\#219](https://github.com/ddeboer/imap/pull/219) ([Slamdunk](https://github.com/Slamdunk)) +- README.md: call Connection::expunge after move and delete [\#210](https://github.com/ddeboer/imap/pull/210) ([Slamdunk](https://github.com/Slamdunk)) +- Remove misleading Mailbox::expunge\(\) [\#206](https://github.com/ddeboer/imap/pull/206) ([Slamdunk](https://github.com/Slamdunk)) +- Add CHANGELOG.md [\#194](https://github.com/ddeboer/imap/pull/194) ([Slamdunk](https://github.com/Slamdunk)) +- README.md updates [\#178](https://github.com/ddeboer/imap/pull/178) ([Slamdunk](https://github.com/Slamdunk)) + +## [0.5.2](https://github.com/ddeboer/imap/tree/0.5.2) (2015-12-03) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.5.1...0.5.2) + +**Closed issues:** + +- $message-\>getAttachments\(\) returns null if message has no attachments [\#80](https://github.com/ddeboer/imap/issues/80) +- Email objects visibility [\#76](https://github.com/ddeboer/imap/issues/76) + +**Merged pull requests:** + +- Fixed the keepUnseen method [\#95](https://github.com/ddeboer/imap/pull/95) ([aeyoll](https://github.com/aeyoll)) +- Mark Mailbox as countable, fix doc comments [\#91](https://github.com/ddeboer/imap/pull/91) ([krzysiekpiasecki](https://github.com/krzysiekpiasecki)) +- Message::getAttachments confirm to signature [\#82](https://github.com/ddeboer/imap/pull/82) ([boekkooi](https://github.com/boekkooi)) +- Added hasMailbox to Connection [\#81](https://github.com/ddeboer/imap/pull/81) ([boekkooi](https://github.com/boekkooi)) +- Make sure imap connection are reopened [\#79](https://github.com/ddeboer/imap/pull/79) ([joserobleda](https://github.com/joserobleda)) + +## [0.5.1](https://github.com/ddeboer/imap/tree/0.5.1) (2015-02-01) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.5.0...0.5.1) + +**Closed issues:** + +- imap\_open error [\#72](https://github.com/ddeboer/imap/issues/72) +- $message-\>getAttachments\(\) does not return anything, even though a message has at least one attachment [\#71](https://github.com/ddeboer/imap/issues/71) +- Prepare docs for 1.0 [\#69](https://github.com/ddeboer/imap/issues/69) +- "date" header is not reliable [\#63](https://github.com/ddeboer/imap/issues/63) +- File Attachments don't show up [\#55](https://github.com/ddeboer/imap/issues/55) + +**Merged pull requests:** + +- Add support for attachments without content disposition [\#70](https://github.com/ddeboer/imap/pull/70) ([ddeboer](https://github.com/ddeboer)) + +## [0.5.0](https://github.com/ddeboer/imap/tree/0.5.0) (2015-01-24) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.4.0...0.5.0) + +**Closed issues:** + +- Use utf8\_encode\(\) function to encode content [\#66](https://github.com/ddeboer/imap/issues/66) +- Please add function order by date [\#59](https://github.com/ddeboer/imap/issues/59) +- mb\_convert\_encoding breaks code [\#57](https://github.com/ddeboer/imap/issues/57) +- How get I getMessages but newest first ... [\#11](https://github.com/ddeboer/imap/issues/11) + +## [0.4.0](https://github.com/ddeboer/imap/tree/0.4.0) (2015-01-04) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.3.1...0.4.0) + +**Closed issues:** + +- Please add 6th parameter to imap\_open call [\#62](https://github.com/ddeboer/imap/issues/62) +- Should Message::delete\(\) use the Message UID? [\#46](https://github.com/ddeboer/imap/issues/46) +- mb\_convert\_encoding\(\): Illegal character encoding specified [\#35](https://github.com/ddeboer/imap/issues/35) +- Deleting a message isn't working [\#30](https://github.com/ddeboer/imap/issues/30) +- imap\_header doesn't work with message uid [\#26](https://github.com/ddeboer/imap/issues/26) + +**Merged pull requests:** + +- Added basic requirement [\#61](https://github.com/ddeboer/imap/pull/61) ([nikoskip](https://github.com/nikoskip)) +- FIX: PHP error: "Cannot declare class Ddeboer\Imap\Search\Text\Text ..." [\#58](https://github.com/ddeboer/imap/pull/58) ([racztiborzoltan](https://github.com/racztiborzoltan)) +- Message::delete sets the FT\_UID flag. Fixes \#30 Fixes \#46 [\#54](https://github.com/ddeboer/imap/pull/54) ([ctalbot](https://github.com/ctalbot)) +- Allow binary-encoded part content [\#48](https://github.com/ddeboer/imap/pull/48) ([joker806](https://github.com/joker806)) +- Fix CS [\#47](https://github.com/ddeboer/imap/pull/47) ([xelan](https://github.com/xelan)) +- fixed typo [\#45](https://github.com/ddeboer/imap/pull/45) ([xelan](https://github.com/xelan)) + +## [0.3.1](https://github.com/ddeboer/imap/tree/0.3.1) (2014-08-11) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.3.0...0.3.1) + +**Merged pull requests:** + +- \imap\_header dosen't work with UID [\#44](https://github.com/ddeboer/imap/pull/44) ([ysramirez](https://github.com/ysramirez)) + +## [0.3.0](https://github.com/ddeboer/imap/tree/0.3.0) (2014-08-10) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.2...0.3.0) + +**Closed issues:** + +- please remove useless wiki [\#42](https://github.com/ddeboer/imap/issues/42) +- Travis tests allways fail? [\#40](https://github.com/ddeboer/imap/issues/40) +- Garbled e-mail body encoding [\#27](https://github.com/ddeboer/imap/issues/27) +- Improve docs [\#25](https://github.com/ddeboer/imap/issues/25) +- "undisclosed-recipients" throws error [\#23](https://github.com/ddeboer/imap/issues/23) + +**Merged pull requests:** + +- correct minor typo [\#43](https://github.com/ddeboer/imap/pull/43) ([cordoval](https://github.com/cordoval)) +- Utf-8 encode body content. [\#39](https://github.com/ddeboer/imap/pull/39) ([cmoralesweb](https://github.com/cmoralesweb)) +- Fix regex parsing the date header \(allowing multiple brackets\) [\#38](https://github.com/ddeboer/imap/pull/38) ([joker806](https://github.com/joker806)) +- Allow empty connection flags [\#34](https://github.com/ddeboer/imap/pull/34) ([joker806](https://github.com/joker806)) +- Fixed typo [\#32](https://github.com/ddeboer/imap/pull/32) ([abhinavkumar940](https://github.com/abhinavkumar940)) + +## [0.2](https://github.com/ddeboer/imap/tree/0.2) (2013-11-24) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.1...0.2) + +## [0.1](https://github.com/ddeboer/imap/tree/0.1) (2013-11-22) +**Closed issues:** + +- Prevent setting SEEN flag [\#20](https://github.com/ddeboer/imap/issues/20) +- Add tests [\#18](https://github.com/ddeboer/imap/issues/18) +- delete messages [\#9](https://github.com/ddeboer/imap/issues/9) +- README is missing basic usage [\#7](https://github.com/ddeboer/imap/issues/7) +- Subject and other texts are decoded incorrectly [\#3](https://github.com/ddeboer/imap/issues/3) + +**Merged pull requests:** + +- also fetch inline attachments [\#24](https://github.com/ddeboer/imap/pull/24) ([kaiserlos](https://github.com/kaiserlos)) +- since leading slash is always needed [\#22](https://github.com/ddeboer/imap/pull/22) ([huglester](https://github.com/huglester)) +- Added missed createMailbox\($name\) function [\#19](https://github.com/ddeboer/imap/pull/19) ([burci](https://github.com/burci)) +- Added move and delete function to message + expunge function [\#17](https://github.com/ddeboer/imap/pull/17) ([burci](https://github.com/burci)) +- Clean up some unused variable [\#16](https://github.com/ddeboer/imap/pull/16) ([burci](https://github.com/burci)) +- Fixed mailbox encoding [\#15](https://github.com/ddeboer/imap/pull/15) ([burci](https://github.com/burci)) +- Create new mailbox [\#14](https://github.com/ddeboer/imap/pull/14) ([burci](https://github.com/burci)) +- Fixed bug in getDecodedContent with 'format=flowed' email [\#13](https://github.com/ddeboer/imap/pull/13) ([burci](https://github.com/burci)) +- Fixed date parsing for some imap servers [\#12](https://github.com/ddeboer/imap/pull/12) ([thelfensdrfer](https://github.com/thelfensdrfer)) +- Add support for more complex search expressions. [\#10](https://github.com/ddeboer/imap/pull/10) ([jamesiarmes](https://github.com/jamesiarmes)) +- Allow user to change server connection flags [\#6](https://github.com/ddeboer/imap/pull/6) ([mvar](https://github.com/mvar)) +- Improvements in EmailAddress class [\#4](https://github.com/ddeboer/imap/pull/4) ([mvar](https://github.com/mvar)) + + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/data/web/inc/lib/vendor/ddeboer/imap/LICENSE b/data/web/inc/lib/vendor/ddeboer/imap/LICENSE new file mode 100644 index 00000000..2c679e30 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2013 David de Boer <david@ddeboer.nl> + +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. \ No newline at end of file diff --git a/data/web/inc/lib/vendor/ddeboer/imap/README.md b/data/web/inc/lib/vendor/ddeboer/imap/README.md new file mode 100644 index 00000000..250f0504 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/README.md @@ -0,0 +1,356 @@ +# IMAP library + +[](https://travis-ci.org/ddeboer/imap) +[](https://scrutinizer-ci.com/g/ddeboer/imap/?branch=master) +[](https://scrutinizer-ci.com/g/ddeboer/imap/?branch=master) +[](https://packagist.org/packages/ddeboer/imap) +[](https://packagist.org/packages/ddeboer/imap) + +A PHP 7.1+ library to read and process e-mails over IMAP. + +This library requires [IMAP](https://secure.php.net/manual/en/book.imap.php), +[iconv](https://secure.php.net/manual/en/book.iconv.php) and +[Multibyte String](https://secure.php.net/manual/en/book.mbstring.php) extensions installed. + +## Table of Contents + +1. [Installation](#installation) +1. [Usage](#usage) + 1. [Connect and Authenticate](#connect-and-authenticate) + 1. [Mailboxes](#mailboxes) + 1. [Messages](#messages) + 1. [Searching for Messages](#searching-for-messages) + 1. [Unknown search criterion: OR](#unknown-search-criterion-or) + 1. [Message Properties and Operations](#message-properties-and-operations) + 1. [Message Attachments](#message-attachments) + 1. [Embedded Messages](#embedded-messages) + 1. [Timeouts](#timeouts) +1. [Mock the library](#mock-the-library) +1. [Running the Tests](#running-the-tests) + 1. [Running Tests using Docker](#running-tests-using-docker) + +## Installation + +The recommended way to install the IMAP library is through [Composer](https://getcomposer.org): + +```bash +$ composer require ddeboer/imap +``` + +This command requires you to have Composer installed globally, as explained +in the [installation chapter](https://getcomposer.org/doc/00-intro.md) +of the Composer documentation. + +## Usage + +### Connect and Authenticate + +```php +use Ddeboer\Imap\Server; + +$server = new Server('imap.gmail.com'); + +// $connection is instance of \Ddeboer\Imap\Connection +$connection = $server->authenticate('my_username', 'my_password'); +``` + +You can specify port, [flags and parameters](https://secure.php.net/manual/en/function.imap-open.php) +to the server: + +```php +$server = new Server( + $hostname, // required + $port, // defaults to '993' + $flags, // defaults to '/imap/ssl/validate-cert' + $parameters +); +``` + +### Mailboxes + +Retrieve mailboxes (also known as mail folders) from the mail server and iterate +over them: + +```php +$mailboxes = $connection->getMailboxes(); + +foreach ($mailboxes as $mailbox) { + // Skip container-only mailboxes + // @see https://secure.php.net/manual/en/function.imap-getmailboxes.php + if ($mailbox->getAttributes() & \LATT_NOSELECT) { + continue; + } + + // $mailbox is instance of \Ddeboer\Imap\Mailbox + printf('Mailbox "%s" has %s messages', $mailbox->getName(), $mailbox->count()); +} +``` + +Or retrieve a specific mailbox: + +```php +$mailbox = $connection->getMailbox('INBOX'); +``` + +Delete a mailbox: + +```php +$connection->deleteMailbox($mailbox); +``` + +You can bulk set, or clear, any [flag](https://secure.php.net/manual/en/function.imap-setflag-full.php) of mailbox messages (by UIDs): + +```php +$mailbox->setFlag('\\Seen \\Flagged', ['1:5', '7', '9']); +$mailbox->setFlag('\\Seen', '1,3,5,6:8'); + +$mailbox->clearFlag('\\Flagged', '1,3'); +``` + +**WARNING** You must retrieve new Message instances in case of bulk modify flags to refresh the single Messages flags. + +### Messages + +Retrieve messages (e-mails) from a mailbox and iterate over them: + +```php +$messages = $mailbox->getMessages(); + +foreach ($messages as $message) { + // $message is instance of \Ddeboer\Imap\Message +} +``` + +To insert a new message (that just has been sent) into the Sent mailbox and flag it as seen: + +```php +$mailbox = $connection->getMailbox('Sent'); +$mailbox->addMessage($messageMIME, '\\Seen'); +``` + +Note that the message should be a string at MIME format (as described in the [RFC2045](https://tools.ietf.org/html/rfc2045)). + +#### Searching for Messages + +```php +use Ddeboer\Imap\SearchExpression; +use Ddeboer\Imap\Search\Email\To; +use Ddeboer\Imap\Search\Text\Body; + +$search = new SearchExpression(); +$search->addCondition(new To('me@here.com')); +$search->addCondition(new Body('contents')); + +$messages = $mailbox->getMessages($search); +``` + +**WARNING** We are currently unable to have both spaces _and_ double-quotes +escaped together. Only spaces are currently escaped correctly. +You can use `Ddeboer\Imap\Search\RawExpression` to write the complete search +condition by yourself. + +Messages can also be retrieved sorted as per [imap_sort](https://secure.php.net/manual/en/function.imap-sort.php) +function: + +```php +$today = new DateTimeImmutable(); +$lastMonth = $today->sub(new DateInterval('P30D')); + +$messages = $mailbox->getMessages( + new Ddeboer\Imap\Search\Date\Since($lastMonth), + \SORTDATE, // Sort criteria + true // Descending order +); +``` + +#### Unknown search criterion: OR + +Note that PHP imap library relies on the `c-client` library available at https://www.washington.edu/imap/ +which doesn't fully support some IMAP4 search criteria like `OR`. If you want those unsupported criteria, +you need to manually patch the latest version (`imap-2007f` of 23-Jul-2011 at the time of this commit) +and recompile PHP onto your patched `c-client` library. + +By the way most of the common search criteria are available and functioning, browse them in `./src/Search`. + +References: + +1. https://stackoverflow.com/questions/36356715/imap-search-unknown-search-criterion-or +1. imap-2007f.tar.gz: `./src/c-client/mail.c` and `./docs/internal.txt` + +#### Message Properties and Operations + +Get message number and unique [message id](https://en.wikipedia.org/wiki/Message-ID) +in the form <...>: + +```php +$message->getNumber(); +$message->getId(); +``` + +Get other message properties: + +```php +$message->getSubject(); +$message->getFrom(); // Message\EmailAddress +$message->getTo(); // array of Message\EmailAddress +$message->getDate(); // DateTimeImmutable +$message->isAnswered(); +$message->isDeleted(); +$message->isDraft(); +$message->isSeen(); +``` + +Get message headers as a [\Ddeboer\Imap\Message\Headers](/src/Ddeboer/Imap/Message/Headers.php) object: + +```php +$message->getHeaders(); +``` + +Get message body as HTML or plain text: + +```php +$message->getBodyHtml(); // Content of text/html part, if present +$message->getBodyText(); // Content of text/plain part, if present +``` + +Reading the message body keeps the message as unseen. +If you want to mark the message as seen: + +```php +$message->markAsSeen(); +``` + +Or you can set, or clear, any [flag](https://secure.php.net/manual/en/function.imap-setflag-full.php): + +```php +$message->setFlag('\\Seen \\Flagged'); +$message->clearFlag('\\Flagged'); +``` + +Move a message to another mailbox: + +```php +$mailbox = $connection->getMailbox('another-mailbox'); +$message->move($mailbox); +$connection->expunge(); +``` + +Deleting messages: + +```php +$mailbox->getMessage(1)->delete(); +$mailbox->getMessage(2)->delete(); +$connection->expunge(); +``` + +### Message Attachments + +Get message attachments (both inline and attached) and iterate over them: + +```php +$attachments = $message->getAttachments(); + +foreach ($attachments as $attachment) { + // $attachment is instance of \Ddeboer\Imap\Message\Attachment +} +``` + +Download a message attachment to a local file: + +```php +// getDecodedContent() decodes the attachmentโs contents automatically: +file_put_contents( + '/my/local/dir/' . $attachment->getFilename(), + $attachment->getDecodedContent() +); +``` + +### Embedded Messages + +Check if attachment is embedded message and get it: + +```php +$attachments = $message->getAttachments(); + +foreach ($attachments as $attachment) { + if ($attachment->isEmbeddedMessage()) { + $embeddedMessage = $attachment->getEmbeddedMessage(); + // $embeddedMessage is instance of \Ddeboer\Imap\Message\EmbeddedMessage + } +} +``` + +An EmbeddedMessage has the same API as a normal Message, apart from flags +and operations like copy, move or delete. + +### Timeouts + +The IMAP extension provides the [imap_timeout](https://secure.php.net/manual/en/function.imap-timeout.php) +function to adjust the timeout seconds for various operations. + +However the extension's implementation doesn't link the functionality to a +specific context or connection, instead they are global. So in order to not +affect functionalities outside this library, we had to choose whether wrap +every `imap_*` call around an optional user-provided timeout or leave this +task to the user. + +Because of the heterogeneous world of IMAP servers and the high complexity +burden cost for such a little gain of the former, we chose the latter. + +## Mock the library + +Mockability is granted by interfaces present for each API. +Dig into [MockabilityTest](tests/MockabilityTest.php) for an example of a +mocked workflow. + +## Running the Tests + +This library is functionally tested on [Travis CI](https://travis-ci.org/ddeboer/imap) +against a local Dovecot server. + +If you have your own IMAP (test) account, you can run the tests locally by +providing your IMAP credentials: + +```bash +$ composer install +$ IMAP_SERVER_NAME="my.imap.server.com" IMAP_SERVER_PORT="60993" IMAP_USERNAME="johndoe" IMAP_PASSWORD="p4ssword" vendor/bin/phpunit +``` + +You can also copy `phpunit.xml.dist` file to a custom `phpunit.xml` and put +these environment variables in it: + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<phpunit + bootstrap="./vendor/autoload.php" + colors="true" + verbose="true" +> + <testsuites> + <testsuite name="ddeboer/imap"> + <directory>./tests/</directory> + </testsuite> + </testsuites> + <filter> + <whitelist> + <directory suffix=".php">./src</directory> + </whitelist> + </filter> + <php> + <env name="IMAP_SERVER_NAME" value="my.imap.server.com" /> + <env name="IMAP_SERVER_PORT" value="60993" /> + <env name="IMAP_USERNAME" value="johndoe" /> + <env name="IMAP_PASSWORD" value="p4ssword" /> + </php> +</phpunit> +``` + +**WARNING** Tests create new mailboxes without removing them. + +### Running Tests using Docker + +If you have Docker installed you can run the tests locally with the following command: + +``` +$ docker-compose run tests +``` diff --git a/data/web/inc/lib/vendor/ddeboer/imap/composer.json b/data/web/inc/lib/vendor/ddeboer/imap/composer.json new file mode 100644 index 00000000..2286f310 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/composer.json @@ -0,0 +1,47 @@ +{ + "name": "ddeboer/imap", + "description": "Object-oriented IMAP for PHP", + "keywords": [ + "email", + "mail", + "imap" + ], + "license": "MIT", + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + } + ], + "require": { + "php": "^7.1", + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.13", + "phpstan/phpstan": "^0.9.1", + "phpstan/phpstan-phpunit": "^0.9.3", + "phpunit/phpunit": "^7.4", + "zendframework/zend-mail": "^2.10" + }, + "autoload": { + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Ddeboer\\Imap\\Tests\\": "tests/" + } + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php new file mode 100644 index 00000000..a12ec789 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php @@ -0,0 +1,215 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +use Ddeboer\Imap\Exception\CreateMailboxException; +use Ddeboer\Imap\Exception\DeleteMailboxException; +use Ddeboer\Imap\Exception\ImapGetmailboxesException; +use Ddeboer\Imap\Exception\InvalidResourceException; +use Ddeboer\Imap\Exception\MailboxDoesNotExistException; + +/** + * A connection to an IMAP server that is authenticated for a user. + */ +final class Connection implements ConnectionInterface +{ + /** + * @var ImapResourceInterface + */ + private $resource; + + /** + * @var string + */ + private $server; + + /** + * @var null|array + */ + private $mailboxes; + + /** + * @var null|array + */ + private $mailboxNames; + + /** + * Constructor. + * + * @param ImapResourceInterface $resource + * @param string $server + * + * @throws \InvalidArgumentException + */ + public function __construct(ImapResourceInterface $resource, string $server) + { + $this->resource = $resource; + $this->server = $server; + } + + /** + * Get IMAP resource. + * + * @return ImapResourceInterface + */ + public function getResource(): ImapResourceInterface + { + return $this->resource; + } + + /** + * Delete all messages marked for deletion. + * + * @return bool + */ + public function expunge(): bool + { + return \imap_expunge($this->resource->getStream()); + } + + /** + * Close connection. + * + * @param int $flag + * + * @return bool + */ + public function close(int $flag = 0): bool + { + return \imap_close($this->resource->getStream(), $flag); + } + + /** + * Get a list of mailboxes (also known as folders). + * + * @return MailboxInterface[] + */ + public function getMailboxes(): array + { + $this->initMailboxNames(); + + if (null === $this->mailboxes) { + $this->mailboxes = []; + foreach ($this->mailboxNames as $mailboxName => $mailboxInfo) { + $this->mailboxes[(string) $mailboxName] = $this->getMailbox((string) $mailboxName); + } + } + + return $this->mailboxes; + } + + /** + * Check that a mailbox with the given name exists. + * + * @param string $name Mailbox name + * + * @return bool + */ + public function hasMailbox(string $name): bool + { + $this->initMailboxNames(); + + return isset($this->mailboxNames[$name]); + } + + /** + * Get a mailbox by its name. + * + * @param string $name Mailbox name + * + * @throws MailboxDoesNotExistException If mailbox does not exist + * + * @return MailboxInterface + */ + public function getMailbox(string $name): MailboxInterface + { + if (false === $this->hasMailbox($name)) { + throw new MailboxDoesNotExistException(\sprintf('Mailbox name "%s" does not exist', $name)); + } + + return new Mailbox($this->resource, $name, $this->mailboxNames[$name]); + } + + /** + * Count number of messages not in any mailbox. + * + * @return int + */ + public function count() + { + return \imap_num_msg($this->resource->getStream()); + } + + /** + * Check if the connection is still active. + * + * @throws InvalidResourceException If connection was closed + * + * @return bool + */ + public function ping(): bool + { + return \imap_ping($this->resource->getStream()); + } + + /** + * Create mailbox. + * + * @param string $name + * + * @throws CreateMailboxException + * + * @return MailboxInterface + */ + public function createMailbox(string $name): MailboxInterface + { + if (false === \imap_createmailbox($this->resource->getStream(), $this->server . \mb_convert_encoding($name, 'UTF7-IMAP', 'UTF-8'))) { + throw new CreateMailboxException(\sprintf('Can not create "%s" mailbox at "%s"', $name, $this->server)); + } + + $this->mailboxNames = $this->mailboxes = null; + $this->resource->clearLastMailboxUsedCache(); + + return $this->getMailbox($name); + } + + /** + * Create mailbox. + * + * @param MailboxInterface $mailbox + * + * @throws DeleteMailboxException + */ + public function deleteMailbox(MailboxInterface $mailbox): void + { + if (false === \imap_deletemailbox($this->resource->getStream(), $mailbox->getFullEncodedName())) { + throw new DeleteMailboxException(\sprintf('Mailbox "%s" could not be deleted', $mailbox->getName())); + } + + $this->mailboxes = $this->mailboxNames = null; + $this->resource->clearLastMailboxUsedCache(); + } + + /** + * Get mailbox names. + */ + private function initMailboxNames(): void + { + if (null !== $this->mailboxNames) { + return; + } + + $this->mailboxNames = []; + $mailboxesInfo = \imap_getmailboxes($this->resource->getStream(), $this->server, '*'); + if (!\is_array($mailboxesInfo)) { + throw new ImapGetmailboxesException('imap_getmailboxes failed'); + } + + foreach ($mailboxesInfo as $mailboxInfo) { + $name = \mb_convert_encoding(\str_replace($this->server, '', $mailboxInfo->name), 'UTF-8', 'UTF7-IMAP'); + $this->mailboxNames[$name] = $mailboxInfo; + } + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php new file mode 100644 index 00000000..ac181657 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php @@ -0,0 +1,82 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +/** + * A connection to an IMAP server that is authenticated for a user. + */ +interface ConnectionInterface extends \Countable +{ + /** + * Get IMAP resource. + * + * @return ImapResourceInterface + */ + public function getResource(): ImapResourceInterface; + + /** + * Delete all messages marked for deletion. + * + * @return bool + */ + public function expunge(): bool; + + /** + * Close connection. + * + * @param int $flag + * + * @return bool + */ + public function close(int $flag = 0): bool; + + /** + * Check if the connection is still active. + * + * @return bool + */ + public function ping(): bool; + + /** + * Get a list of mailboxes (also known as folders). + * + * @return MailboxInterface[] + */ + public function getMailboxes(): array; + + /** + * Check that a mailbox with the given name exists. + * + * @param string $name Mailbox name + * + * @return bool + */ + public function hasMailbox(string $name): bool; + + /** + * Get a mailbox by its name. + * + * @param string $name Mailbox name + * + * @return MailboxInterface + */ + public function getMailbox(string $name): MailboxInterface; + + /** + * Create mailbox. + * + * @param string $name + * + * @return MailboxInterface + */ + public function createMailbox(string $name): MailboxInterface; + + /** + * Create mailbox. + * + * @param MailboxInterface $mailbox + */ + public function deleteMailbox(MailboxInterface $mailbox): void; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php new file mode 100644 index 00000000..10a1518e --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +abstract class AbstractException extends \RuntimeException +{ + /** + * @var array + */ + private static $errorLabels = [ + \E_ERROR => 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + \E_STRICT => 'E_STRICT', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + ]; + + /** + * @param string $message The exception message + * @param int $code The exception code + * @param \Throwable $previous The previous exception + */ + final public function __construct(string $message, int $code = 0, \Throwable $previous = null) + { + $errorType = ''; + if (\is_int($code) && isset(self::$errorLabels[$code])) { + $errorType = \sprintf('[%s] ', self::$errorLabels[$code]); + } + + $joinString = "\n- "; + $alerts = \imap_alerts(); + $errors = \imap_errors(); + $completeMessage = \sprintf( + "%s%s\nimap_alerts (%s):%s\nimap_errors (%s):%s", + $errorType, + $message, + $alerts ? \count($alerts) : 0, + $alerts ? $joinString . \implode($joinString, $alerts) : '', + $errors ? \count($errors) : 0, + $errors ? $joinString . \implode($joinString, $errors) : '' + ); + + parent::__construct($completeMessage, $code, $previous); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php new file mode 100644 index 00000000..c0e93d00 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class AuthenticationFailedException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/CreateMailboxException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/CreateMailboxException.php new file mode 100644 index 00000000..55bb3a94 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/CreateMailboxException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class CreateMailboxException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/DeleteMailboxException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/DeleteMailboxException.php new file mode 100644 index 00000000..694c3a66 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/DeleteMailboxException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class DeleteMailboxException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapGetmailboxesException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapGetmailboxesException.php new file mode 100644 index 00000000..7e68326b --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapGetmailboxesException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class ImapGetmailboxesException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidDateHeaderException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidDateHeaderException.php new file mode 100644 index 00000000..05401e22 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidDateHeaderException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class InvalidDateHeaderException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidHeadersException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidHeadersException.php new file mode 100644 index 00000000..9554527a --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidHeadersException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class InvalidHeadersException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidResourceException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidResourceException.php new file mode 100644 index 00000000..f3cbf5b6 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidResourceException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class InvalidResourceException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidSearchCriteriaException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidSearchCriteriaException.php new file mode 100644 index 00000000..783c6ba1 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidSearchCriteriaException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class InvalidSearchCriteriaException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MailboxDoesNotExistException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MailboxDoesNotExistException.php new file mode 100644 index 00000000..0cdf28a6 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MailboxDoesNotExistException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class MailboxDoesNotExistException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageCopyException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageCopyException.php new file mode 100644 index 00000000..aa1ab687 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageCopyException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class MessageCopyException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageDeleteException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageDeleteException.php new file mode 100644 index 00000000..38d88ff9 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageDeleteException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class MessageDeleteException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageDoesNotExistException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageDoesNotExistException.php new file mode 100644 index 00000000..1485a635 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageDoesNotExistException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class MessageDoesNotExistException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageMoveException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageMoveException.php new file mode 100644 index 00000000..2f0cd0c1 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageMoveException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class MessageMoveException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageStructureException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageStructureException.php new file mode 100644 index 00000000..a0dd4364 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageStructureException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class MessageStructureException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/NotEmbeddedMessageException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/NotEmbeddedMessageException.php new file mode 100644 index 00000000..12afdec4 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/NotEmbeddedMessageException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class NotEmbeddedMessageException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/OutOfBoundsException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/OutOfBoundsException.php new file mode 100644 index 00000000..55528c1b --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/OutOfBoundsException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class OutOfBoundsException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ReopenMailboxException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ReopenMailboxException.php new file mode 100644 index 00000000..945c6548 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ReopenMailboxException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class ReopenMailboxException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/UnexpectedEncodingException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/UnexpectedEncodingException.php new file mode 100644 index 00000000..7ec3943e --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/UnexpectedEncodingException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class UnexpectedEncodingException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/UnsupportedCharsetException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/UnsupportedCharsetException.php new file mode 100644 index 00000000..5743679e --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/UnsupportedCharsetException.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Exception; + +final class UnsupportedCharsetException extends AbstractException +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResource.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResource.php new file mode 100644 index 00000000..e40322be --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResource.php @@ -0,0 +1,107 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +use Ddeboer\Imap\Exception\InvalidResourceException; +use Ddeboer\Imap\Exception\ReopenMailboxException; + +/** + * An imap resource stream. + */ +final class ImapResource implements ImapResourceInterface +{ + /** + * @var resource + */ + private $resource; + + /** + * @var null|MailboxInterface + */ + private $mailbox; + + /** + * @var null|string + */ + private static $lastMailboxUsedCache; + + /** + * Constructor. + * + * @param resource $resource + */ + public function __construct($resource, MailboxInterface $mailbox = null) + { + $this->resource = $resource; + $this->mailbox = $mailbox; + } + + /** + * Get IMAP resource stream. + * + * @throws InvalidResourceException + * + * @return resource + */ + public function getStream() + { + if (false === \is_resource($this->resource) || 'imap' !== \get_resource_type($this->resource)) { + throw new InvalidResourceException('Supplied resource is not a valid imap resource'); + } + + $this->initMailbox(); + + return $this->resource; + } + + /** + * Clear last mailbox used cache. + */ + public function clearLastMailboxUsedCache(): void + { + self::$lastMailboxUsedCache = null; + } + + /** + * If connection is not currently in this mailbox, switch it to this mailbox. + */ + private function initMailbox(): void + { + if (null === $this->mailbox || $this->isMailboxOpen()) { + return; + } + + \imap_reopen($this->resource, $this->mailbox->getFullEncodedName()); + + if ($this->isMailboxOpen()) { + return; + } + + throw new ReopenMailboxException(\sprintf('Cannot reopen mailbox "%s"', $this->mailbox->getName())); + } + + /** + * Check whether the current mailbox is open. + * + * @return bool + */ + private function isMailboxOpen(): bool + { + $currentMailboxName = $this->mailbox->getFullEncodedName(); + if ($currentMailboxName === self::$lastMailboxUsedCache) { + return true; + } + + self::$lastMailboxUsedCache = null; + $check = \imap_check($this->resource); + $return = false !== $check && $check->Mailbox === $currentMailboxName; + + if (true === $return) { + self::$lastMailboxUsedCache = $currentMailboxName; + } + + return $return; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php new file mode 100644 index 00000000..03c16f7e --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +interface ImapResourceInterface +{ + /** + * Get IMAP resource stream. + * + * @return resource + */ + public function getStream(); + + /** + * Clear last mailbox used cache. + */ + public function clearLastMailboxUsedCache(): void; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php new file mode 100644 index 00000000..62fcc1d4 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php @@ -0,0 +1,319 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +use DateTimeInterface; +use Ddeboer\Imap\Exception\InvalidSearchCriteriaException; +use Ddeboer\Imap\Exception\MessageCopyException; +use Ddeboer\Imap\Exception\MessageMoveException; +use Ddeboer\Imap\Search\ConditionInterface; +use Ddeboer\Imap\Search\LogicalOperator\All; + +/** + * An IMAP mailbox (commonly referred to as a 'folder'). + */ +final class Mailbox implements MailboxInterface +{ + /** + * @var ImapResourceInterface + */ + private $resource; + + /** + * @var string + */ + private $name; + + /** + * @var \stdClass + */ + private $info; + + /** + * Constructor. + * + * @param ImapResourceInterface $resource IMAP resource + * @param string $name Mailbox decoded name + * @param \stdClass $info Mailbox info + */ + public function __construct(ImapResourceInterface $resource, string $name, \stdClass $info) + { + $this->resource = new ImapResource($resource->getStream(), $this); + $this->name = $name; + $this->info = $info; + } + + /** + * Get mailbox decoded name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get mailbox encoded path. + * + * @return string + */ + public function getEncodedName(): string + { + return \preg_replace('/^{.+}/', '', $this->info->name); + } + + /** + * Get mailbox encoded full name. + * + * @return string + */ + public function getFullEncodedName(): string + { + return $this->info->name; + } + + /** + * Get mailbox attributes. + * + * @return int + */ + public function getAttributes(): int + { + return $this->info->attributes; + } + + /** + * Get mailbox delimiter. + * + * @return string + */ + public function getDelimiter(): string + { + return $this->info->delimiter; + } + + /** + * Get number of messages in this mailbox. + * + * @return int + */ + public function count() + { + return \imap_num_msg($this->resource->getStream()); + } + + /** + * Get Mailbox status. + * + * @param null|int $flags + * + * @return \stdClass + */ + public function getStatus(int $flags = null): \stdClass + { + return \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL); + } + + /** + * Bulk Set Flag for Messages. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * @param array|MessageIterator|string $numbers Message numbers + * + * @return bool + */ + public function setFlag(string $flag, $numbers): bool + { + return \imap_setflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID); + } + + /** + * Bulk Clear Flag for Messages. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * @param array|MessageIterator|string $numbers Message numbers + * + * @return bool + */ + public function clearFlag(string $flag, $numbers): bool + { + return \imap_clearflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID); + } + + /** + * Get message ids. + * + * @param ConditionInterface $search Search expression (optional) + * + * @return MessageIteratorInterface + */ + public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false): MessageIteratorInterface + { + if (null === $search) { + $search = new All(); + } + $query = $search->toString(); + + // We need to clear the stack to know whether imap_last_error() + // is related to this imap_search + \imap_errors(); + + if (null !== $sortCriteria) { + $messageNumbers = \imap_sort($this->resource->getStream(), $sortCriteria, $descending ? 1 : 0, \SE_UID, $query); + } else { + $messageNumbers = \imap_search($this->resource->getStream(), $query, \SE_UID); + } + if (false === $messageNumbers) { + if (false !== \imap_last_error()) { + throw new InvalidSearchCriteriaException(\sprintf('Invalid search criteria [%s]', $query)); + } + + // imap_search can also return false + $messageNumbers = []; + } + + return new MessageIterator($this->resource, $messageNumbers); + } + + /** + * Get message iterator for a sequence. + * + * @param string $sequence Message numbers + * + * @return MessageIteratorInterface + */ + public function getMessageSequence(string $sequence): MessageIteratorInterface + { + \imap_errors(); + + $overview = \imap_fetch_overview($this->resource->getStream(), $sequence, FT_UID); + if (empty($overview)) { + if (false !== \imap_last_error()) { + throw new InvalidSearchCriteriaException(\sprintf('Invalid sequence [%s]', $sequence)); + } + + $messageNumbers = []; + } else { + $messageNumbers = \array_column($overview, 'uid'); + } + + return new MessageIterator($this->resource, $messageNumbers); + } + + /** + * Get a message by message number. + * + * @param int $number Message number + * + * @return MessageInterface + */ + public function getMessage(int $number): MessageInterface + { + return new Message($this->resource, $number); + } + + /** + * Get messages in this mailbox. + * + * @return MessageIteratorInterface + */ + public function getIterator(): MessageIteratorInterface + { + return $this->getMessages(); + } + + /** + * Add a message to the mailbox. + * + * @param string $message + * @param null|string $options + * @param null|DateTimeInterface $internalDate + * + * @return bool + */ + public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool + { + $arguments = [ + $this->resource->getStream(), + $this->getFullEncodedName(), + $message, + ]; + if (null !== $options) { + $arguments[] = $options; + if (null !== $internalDate) { + $arguments[] = $internalDate->format('d-M-Y H:i:s O'); + } + } + + return \imap_append(...$arguments); + } + + /** + * Returns a tree of threaded message for the current Mailbox. + * + * @return array + */ + public function getThread(): array + { + \set_error_handler(function () {}); + + $tree = \imap_thread($this->resource->getStream()); + + \restore_error_handler(); + + return false !== $tree ? $tree : []; + } + + /** + * Bulk move messages. + * + * @param array|MessageIterator|string $numbers Message numbers + * @param MailboxInterface $mailbox Destination Mailbox to move the messages to + * + * @throws \Ddeboer\Imap\Exception\MessageMoveException + */ + public function move($numbers, MailboxInterface $mailbox): void + { + if (!\imap_mail_move($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageMoveException(\sprintf('Messages cannot be moved to "%s"', $mailbox->getName())); + } + } + + /** + * Bulk copy messages. + * + * @param array|MessageIterator|string $numbers Message numbers + * @param MailboxInterface $mailbox Destination Mailbox to copy the messages to + * + * @throws \Ddeboer\Imap\Exception\MessageCopyException + */ + public function copy($numbers, MailboxInterface $mailbox): void + { + if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageCopyException(\sprintf('Messages cannot be copied to "%s"', $mailbox->getName())); + } + } + + /** + * Prepare message ids for the use with bulk functions. + * + * @param array|MessageIterator|string $messageIds Message numbers + * + * @return string + */ + private function prepareMessageIds($messageIds): string + { + if ($messageIds instanceof MessageIterator) { + $messageIds = $messageIds->getArrayCopy(); + } + + if (\is_array($messageIds)) { + $messageIds = \implode(',', $messageIds); + } + + return (string) $messageIds; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php new file mode 100644 index 00000000..59477a41 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php @@ -0,0 +1,150 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +use DateTimeInterface; +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * An IMAP mailbox (commonly referred to as a 'folder'). + */ +interface MailboxInterface extends \Countable, \IteratorAggregate +{ + /** + * Get mailbox decoded name. + * + * @return string + */ + public function getName(): string; + + /** + * Get mailbox encoded path. + * + * @return string + */ + public function getEncodedName(): string; + + /** + * Get mailbox encoded full name. + * + * @return string + */ + public function getFullEncodedName(): string; + + /** + * Get mailbox attributes. + * + * @return int + */ + public function getAttributes(): int; + + /** + * Get mailbox delimiter. + * + * @return string + */ + public function getDelimiter(): string; + + /** + * Get Mailbox status. + * + * @param null|int $flags + * + * @return \stdClass + */ + public function getStatus(int $flags = null): \stdClass; + + /** + * Bulk Set Flag for Messages. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * @param array|MessageIterator|string $numbers Message numbers + * + * @return bool + */ + public function setFlag(string $flag, $numbers): bool; + + /** + * Bulk Clear Flag for Messages. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * @param array|MessageIterator|string $numbers Message numbers + * + * @return bool + */ + public function clearFlag(string $flag, $numbers): bool; + + /** + * Get message ids. + * + * @param ConditionInterface $search Search expression (optional) + * + * @return MessageIteratorInterface + */ + public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false): MessageIteratorInterface; + + /** + * Get message iterator for a sequence. + * + * @param string $sequence Message numbers + * + * @return MessageIteratorInterface + */ + public function getMessageSequence(string $sequence): MessageIteratorInterface; + + /** + * Get a message by message number. + * + * @param int $number Message number + * + * @return MessageInterface + */ + public function getMessage(int $number): MessageInterface; + + /** + * Get messages in this mailbox. + * + * @return MessageIteratorInterface + */ + public function getIterator(): MessageIteratorInterface; + + /** + * Add a message to the mailbox. + * + * @param string $message + * @param null|string $options + * @param null|DateTimeInterface $internalDate + * + * @return bool + */ + public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool; + + /** + * Returns a tree of threaded message for the current Mailbox. + * + * @return array + */ + public function getThread(): array; + + /** + * Bulk move messages. + * + * @param array|MessageIterator|string $numbers Message numbers + * @param MailboxInterface $mailbox Destination Mailbox to move the messages to + * + * @throws \Ddeboer\Imap\Exception\MessageMoveException + */ + public function move($numbers, self $mailbox): void; + + /** + * Bulk copy messages. + * + * @param array|MessageIterator|string $numbers Message numbers + * @param MailboxInterface $mailbox Destination Mailbox to copy the messages to + * + * @throws \Ddeboer\Imap\Exception\MessageCopyException + */ + public function copy($numbers, self $mailbox): void; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php new file mode 100644 index 00000000..c65fa313 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php @@ -0,0 +1,348 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +use Ddeboer\Imap\Exception\InvalidHeadersException; +use Ddeboer\Imap\Exception\MessageCopyException; +use Ddeboer\Imap\Exception\MessageDeleteException; +use Ddeboer\Imap\Exception\MessageDoesNotExistException; +use Ddeboer\Imap\Exception\MessageMoveException; +use Ddeboer\Imap\Exception\MessageStructureException; + +/** + * An IMAP message (e-mail). + */ +final class Message extends Message\AbstractMessage implements MessageInterface +{ + /** + * @var bool + */ + private $messageNumberVerified = false; + + /** + * @var bool + */ + private $structureLoaded = false; + + /** + * @var null|Message\Headers + */ + private $headers; + + /** + * @var null|string + */ + private $rawHeaders; + + /** + * @var null|string + */ + private $rawMessage; + + /** + * Constructor. + * + * @param ImapResourceInterface $resource IMAP resource + * @param int $messageNumber Message number + */ + public function __construct(ImapResourceInterface $resource, int $messageNumber) + { + parent::__construct($resource, $messageNumber, '1', new \stdClass()); + } + + /** + * Lazy load structure. + */ + protected function lazyLoadStructure(): void + { + if (true === $this->structureLoaded) { + return; + } + $this->structureLoaded = true; + + $messageNumber = $this->getNumber(); + + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorMessage = $message; + $errorNumber = $nr; + }); + + $structure = \imap_fetchstructure( + $this->resource->getStream(), + $messageNumber, + \FT_UID + ); + + \restore_error_handler(); + + if (!$structure instanceof \stdClass) { + throw new MessageStructureException(\sprintf( + 'Message "%s" structure is empty: %s', + $messageNumber, + $errorMessage + ), $errorNumber); + } + + $this->setStructure($structure); + } + + /** + * Ensure message exists. + * + * @param int $messageNumber + */ + protected function assertMessageExists(int $messageNumber): void + { + if (true === $this->messageNumberVerified) { + return; + } + $this->messageNumberVerified = true; + + $msgno = \imap_msgno($this->resource->getStream(), $messageNumber); + if (\is_numeric($msgno) && $msgno > 0) { + return; + } + + throw new MessageDoesNotExistException(\sprintf( + 'Message "%s" does not exist', + $messageNumber + )); + } + + /** + * Get raw message headers. + * + * @return string + */ + public function getRawHeaders(): string + { + if (null === $this->rawHeaders) { + $this->rawHeaders = \imap_fetchheader($this->resource->getStream(), $this->getNumber(), \FT_UID); + } + + return $this->rawHeaders; + } + + /** + * Get the raw message, including all headers, parts, etc. unencoded and unparsed. + * + * @return string the raw message + */ + public function getRawMessage(): string + { + if (null === $this->rawMessage) { + $this->rawMessage = $this->doGetContent(''); + } + + return $this->rawMessage; + } + + /** + * Get message headers. + * + * @return Message\Headers + */ + public function getHeaders(): Message\Headers + { + if (null === $this->headers) { + // imap_headerinfo is much faster than imap_fetchheader + // imap_headerinfo returns only a subset of all mail headers, + // but it does include the message flags. + $headers = \imap_headerinfo($this->resource->getStream(), \imap_msgno($this->resource->getStream(), $this->getNumber())); + if (false === $headers) { + // @see https://github.com/ddeboer/imap/issues/358 + throw new InvalidHeadersException(\sprintf('Message "%s" has invalid headers', $this->getNumber())); + } + $this->headers = new Message\Headers($headers); + } + + return $this->headers; + } + + /** + * Clearmessage headers. + */ + private function clearHeaders(): void + { + $this->headers = null; + } + + /** + * Get message recent flag value (from headers). + * + * @return null|string + */ + public function isRecent(): ?string + { + return $this->getHeaders()->get('recent'); + } + + /** + * Get message unseen flag value (from headers). + * + * @return bool + */ + public function isUnseen(): bool + { + return 'U' === $this->getHeaders()->get('unseen'); + } + + /** + * Get message flagged flag value (from headers). + * + * @return bool + */ + public function isFlagged(): bool + { + return 'F' === $this->getHeaders()->get('flagged'); + } + + /** + * Get message answered flag value (from headers). + * + * @return bool + */ + public function isAnswered(): bool + { + return 'A' === $this->getHeaders()->get('answered'); + } + + /** + * Get message deleted flag value (from headers). + * + * @return bool + */ + public function isDeleted(): bool + { + return 'D' === $this->getHeaders()->get('deleted'); + } + + /** + * Get message draft flag value (from headers). + * + * @return bool + */ + public function isDraft(): bool + { + return 'X' === $this->getHeaders()->get('draft'); + } + + /** + * Has the message been marked as read? + * + * @return bool + */ + public function isSeen(): bool + { + return 'N' !== $this->getHeaders()->get('recent') && 'U' !== $this->getHeaders()->get('unseen'); + } + + /** + * Mark message as seen. + * + * @return bool + * + * @deprecated since version 1.1, to be removed in 2.0 + */ + public function maskAsSeen(): bool + { + \trigger_error(\sprintf('%s is deprecated and will be removed in 2.0. Use %s::markAsSeen instead.', __METHOD__, __CLASS__), \E_USER_DEPRECATED); + + return $this->markAsSeen(); + } + + /** + * Mark message as seen. + * + * @return bool + */ + public function markAsSeen(): bool + { + return $this->setFlag('\\Seen'); + } + + /** + * Move message to another mailbox. + * + * @param MailboxInterface $mailbox + * + * @throws MessageCopyException + */ + public function copy(MailboxInterface $mailbox): void + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_mail_copy($this->resource->getStream(), (string) $this->getNumber(), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageCopyException(\sprintf('Message "%s" cannot be copied to "%s"', $this->getNumber(), $mailbox->getName())); + } + } + + /** + * Move message to another mailbox. + * + * @param MailboxInterface $mailbox + * + * @throws MessageMoveException + */ + public function move(MailboxInterface $mailbox): void + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_mail_move($this->resource->getStream(), (string) $this->getNumber(), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageMoveException(\sprintf('Message "%s" cannot be moved to "%s"', $this->getNumber(), $mailbox->getName())); + } + } + + /** + * Delete message. + * + * @throws MessageDeleteException + */ + public function delete(): void + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_delete($this->resource->getStream(), $this->getNumber(), \FT_UID)) { + throw new MessageDeleteException(\sprintf('Message "%s" cannot be deleted', $this->getNumber())); + } + } + + /** + * Set Flag Message. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * + * @return bool + */ + public function setFlag(string $flag): bool + { + $result = \imap_setflag_full($this->resource->getStream(), (string) $this->getNumber(), $flag, \ST_UID); + + $this->clearHeaders(); + + return $result; + } + + /** + * Clear Flag Message. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * + * @return bool + */ + public function clearFlag(string $flag): bool + { + $result = \imap_clearflag_full($this->resource->getStream(), (string) $this->getNumber(), $flag, \ST_UID); + + $this->clearHeaders(); + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php new file mode 100644 index 00000000..a7fdb02e --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php @@ -0,0 +1,292 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +use Ddeboer\Imap\Exception\InvalidDateHeaderException; + +abstract class AbstractMessage extends AbstractPart +{ + /** + * @var null|array + */ + private $attachments; + + /** + * Get message headers. + * + * @return Headers + */ + abstract public function getHeaders(): Headers; + + /** + * Get message id. + * + * A unique message id in the form <...> + * + * @return null|string + */ + final public function getId(): ?string + { + return $this->getHeaders()->get('message_id'); + } + + /** + * Get message sender (from headers). + * + * @return null|EmailAddress + */ + final public function getFrom(): ?EmailAddress + { + $from = $this->getHeaders()->get('from'); + + return null !== $from ? $this->decodeEmailAddress($from[0]) : null; + } + + /** + * Get To recipients. + * + * @return EmailAddress[] Empty array in case message has no To: recipients + */ + final public function getTo(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('to') ?: []); + } + + /** + * Get Cc recipients. + * + * @return EmailAddress[] Empty array in case message has no CC: recipients + */ + final public function getCc(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('cc') ?: []); + } + + /** + * Get Bcc recipients. + * + * @return EmailAddress[] Empty array in case message has no BCC: recipients + */ + final public function getBcc(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('bcc') ?: []); + } + + /** + * Get Reply-To recipients. + * + * @return EmailAddress[] Empty array in case message has no Reply-To: recipients + */ + final public function getReplyTo(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('reply_to') ?: []); + } + + /** + * Get Sender. + * + * @return EmailAddress[] Empty array in case message has no Sender: recipients + */ + final public function getSender(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('sender') ?: []); + } + + /** + * Get Return-Path. + * + * @return EmailAddress[] Empty array in case message has no Return-Path: recipients + */ + final public function getReturnPath(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('return_path') ?: []); + } + + /** + * Get date (from headers). + * + * @return null|\DateTimeImmutable + */ + final public function getDate(): ?\DateTimeImmutable + { + $dateHeader = $this->getHeaders()->get('date'); + if (null === $dateHeader) { + return null; + } + + $alteredValue = $dateHeader; + $alteredValue = \str_replace(',', '', $alteredValue); + $alteredValue = \preg_replace('/^[a-zA-Z]+ ?/', '', $alteredValue); + $alteredValue = \preg_replace('/ +\(.*\)/', '', $alteredValue); + $alteredValue = \preg_replace('/\bUT\b/', 'UTC', $alteredValue); + if (0 === \preg_match('/\d\d:\d\d:\d\d.* [\+\-]\d\d:?\d\d/', $alteredValue)) { + $alteredValue .= ' +0000'; + } + + try { + $date = new \DateTimeImmutable($alteredValue); + } catch (\Throwable $ex) { + throw new InvalidDateHeaderException(\sprintf('Invalid Date header found: "%s"', $dateHeader), 0, $ex); + } + + return $date; + } + + /** + * Get message size (from headers). + * + * @return null|int|string + */ + final public function getSize() + { + return $this->getHeaders()->get('size'); + } + + /** + * Get message subject (from headers). + * + * @return null|string + */ + final public function getSubject(): ?string + { + return $this->getHeaders()->get('subject'); + } + + /** + * Get message In-Reply-To (from headers). + * + * @return array + */ + final public function getInReplyTo(): array + { + $inReplyTo = $this->getHeaders()->get('in_reply_to'); + + return null !== $inReplyTo ? \explode(' ', $inReplyTo) : []; + } + + /** + * Get message References (from headers). + * + * @return array + */ + final public function getReferences(): array + { + $references = $this->getHeaders()->get('references'); + + return null !== $references ? \explode(' ', $references) : []; + } + + /** + * Get body HTML. + * + * @return null|string + */ + final public function getBodyHtml(): ?string + { + $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $part) { + if (self::SUBTYPE_HTML === $part->getSubtype()) { + return $part->getDecodedContent(); + } + } + + // If message has no parts and is HTML, return content of message itself. + if (self::SUBTYPE_HTML === $this->getSubtype()) { + return $this->getDecodedContent(); + } + + return null; + } + + /** + * Get body text. + * + * @return null|string + */ + final public function getBodyText(): ?string + { + $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $part) { + if (self::SUBTYPE_PLAIN === $part->getSubtype()) { + return $part->getDecodedContent(); + } + } + + // If message has no parts, return content of message itself. + if (self::SUBTYPE_PLAIN === $this->getSubtype()) { + return $this->getDecodedContent(); + } + + return null; + } + + /** + * Get attachments (if any) linked to this e-mail. + * + * @return AttachmentInterface[] + */ + final public function getAttachments(): array + { + if (null === $this->attachments) { + static $gatherAttachments; + if (null === $gatherAttachments) { + $gatherAttachments = static function (PartInterface $part) use (&$gatherAttachments): array { + $attachments = []; + foreach ($part->getParts() as $childPart) { + if ($childPart instanceof Attachment) { + $attachments[] = $childPart; + } + if ($childPart->hasChildren()) { + $attachments = \array_merge($attachments, $gatherAttachments($childPart)); + } + } + + return $attachments; + }; + } + + $this->attachments = $gatherAttachments($this); + } + + return $this->attachments; + } + + /** + * Does this message have attachments? + * + * @return bool + */ + final public function hasAttachments(): bool + { + return \count($this->getAttachments()) > 0; + } + + /** + * @param array $addresses Addesses + * + * @return array + */ + private function decodeEmailAddresses(array $addresses): array + { + $return = []; + foreach ($addresses as $address) { + if (isset($address->mailbox)) { + $return[] = $this->decodeEmailAddress($address); + } + } + + return $return; + } + + /** + * @param \stdClass $value + * + * @return EmailAddress + */ + private function decodeEmailAddress(\stdClass $value): EmailAddress + { + return new EmailAddress($value->mailbox, $value->host, $value->personal); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php new file mode 100644 index 00000000..4be6738c --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php @@ -0,0 +1,578 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +use Ddeboer\Imap\Exception\UnexpectedEncodingException; +use Ddeboer\Imap\ImapResourceInterface; +use Ddeboer\Imap\Message; + +/** + * A message part. + */ +abstract class AbstractPart implements PartInterface +{ + /** + * @var ImapResourceInterface + */ + protected $resource; + + /** + * @var bool + */ + private $structureParsed = false; + + /** + * @var array + */ + private $parts = []; + + /** + * @var string + */ + private $partNumber; + + /** + * @var int + */ + private $messageNumber; + + /** + * @var \stdClass + */ + private $structure; + + /** + * @var Parameters + */ + private $parameters; + + /** + * @var null|string + */ + private $type; + + /** + * @var null|string + */ + private $subtype; + + /** + * @var null|string + */ + private $encoding; + + /** + * @var null|string + */ + private $disposition; + + /** + * @var null|string + */ + private $bytes; + + /** + * @var null|string + */ + private $lines; + + /** + * @var null|string + */ + private $content; + + /** + * @var null|string + */ + private $decodedContent; + + /** + * @var int + */ + private $key = 0; + + /** + * @var array + */ + private static $typesMap = [ + \TYPETEXT => self::TYPE_TEXT, + \TYPEMULTIPART => self::TYPE_MULTIPART, + \TYPEMESSAGE => self::TYPE_MESSAGE, + \TYPEAPPLICATION => self::TYPE_APPLICATION, + \TYPEAUDIO => self::TYPE_AUDIO, + \TYPEIMAGE => self::TYPE_IMAGE, + \TYPEVIDEO => self::TYPE_VIDEO, + \TYPEMODEL => self::TYPE_MODEL, + \TYPEOTHER => self::TYPE_OTHER, + ]; + + /** + * @var array + */ + private static $encodingsMap = [ + \ENC7BIT => self::ENCODING_7BIT, + \ENC8BIT => self::ENCODING_8BIT, + \ENCBINARY => self::ENCODING_BINARY, + \ENCBASE64 => self::ENCODING_BASE64, + \ENCQUOTEDPRINTABLE => self::ENCODING_QUOTED_PRINTABLE, + ]; + + /** + * @var array + */ + private static $attachmentKeys = [ + 'name' => true, + 'filename' => true, + 'name*' => true, + 'filename*' => true, + ]; + + /** + * Constructor. + * + * @param ImapResourceInterface $resource IMAP resource + * @param int $messageNumber Message number + * @param string $partNumber Part number + * @param \stdClass $structure Part structure + */ + public function __construct( + ImapResourceInterface $resource, + int $messageNumber, + string $partNumber, + \stdClass $structure + ) { + $this->resource = $resource; + $this->messageNumber = $messageNumber; + $this->partNumber = $partNumber; + $this->setStructure($structure); + } + + /** + * Get message number (from headers). + * + * @return int + */ + final public function getNumber(): int + { + $this->assertMessageExists($this->messageNumber); + + return $this->messageNumber; + } + + /** + * Ensure message exists. + * + * @param int $messageNumber + */ + protected function assertMessageExists(int $messageNumber): void + { + } + + /** + * @param \stdClass $structure Part structure + */ + final protected function setStructure(\stdClass $structure): void + { + $this->structure = $structure; + } + + /** + * Part structure. + * + * @return \stdClass + */ + final public function getStructure(): \stdClass + { + $this->lazyLoadStructure(); + + return $this->structure; + } + + /** + * Lazy load structure. + */ + protected function lazyLoadStructure(): void + { + } + + /** + * Part parameters. + * + * @return Parameters + */ + final public function getParameters(): Parameters + { + $this->lazyParseStructure(); + + return $this->parameters; + } + + /** + * Part charset. + * + * @return null|string + */ + final public function getCharset(): ?string + { + $this->lazyParseStructure(); + + return $this->parameters->get('charset') ?: null; + } + + /** + * Part type. + * + * @return null|string + */ + final public function getType(): ?string + { + $this->lazyParseStructure(); + + return $this->type; + } + + /** + * Part subtype. + * + * @return null|string + */ + final public function getSubtype(): ?string + { + $this->lazyParseStructure(); + + return $this->subtype; + } + + /** + * Part encoding. + * + * @return null|string + */ + final public function getEncoding(): ?string + { + $this->lazyParseStructure(); + + return $this->encoding; + } + + /** + * Part disposition. + * + * @return null|string + */ + final public function getDisposition(): ?string + { + $this->lazyParseStructure(); + + return $this->disposition; + } + + /** + * Part bytes. + * + * @return null|int|string + */ + final public function getBytes() + { + $this->lazyParseStructure(); + + return $this->bytes; + } + + /** + * Part lines. + * + * @return null|string + */ + final public function getLines(): ?string + { + $this->lazyParseStructure(); + + return $this->lines; + } + + /** + * Get raw part content. + * + * @return string + */ + final public function getContent(): string + { + if (null === $this->content) { + $this->content = $this->doGetContent($this->getContentPartNumber()); + } + + return $this->content; + } + + /** + * Get content part number. + * + * @return string + */ + protected function getContentPartNumber(): string + { + return $this->partNumber; + } + + /** + * Get part number. + * + * @return string + */ + final public function getPartNumber(): string + { + return $this->partNumber; + } + + /** + * Get decoded part content. + * + * @return string + */ + final public function getDecodedContent(): string + { + if (null === $this->decodedContent) { + if (self::ENCODING_UNKNOWN === $this->getEncoding()) { + throw new UnexpectedEncodingException('Cannot decode a content with an uknown encoding'); + } + + $content = $this->getContent(); + if (self::ENCODING_BASE64 === $this->getEncoding()) { + $content = \base64_decode($content); + } elseif (self::ENCODING_QUOTED_PRINTABLE === $this->getEncoding()) { + $content = \quoted_printable_decode($content); + } + + if (false === $content) { + throw new UnexpectedEncodingException('Cannot decode content'); + } + + // If this part is a text part, convert its charset to UTF-8. + // We don't want to decode an attachment's charset. + if (!$this instanceof Attachment && null !== $this->getCharset() && self::TYPE_TEXT === $this->getType()) { + $content = Transcoder::decode($content, $this->getCharset()); + } + + $this->decodedContent = $content; + } + + return $this->decodedContent; + } + + /** + * Get raw message content. + * + * @param string $partNumber + * + * @return string + */ + final protected function doGetContent(string $partNumber): string + { + return \imap_fetchbody( + $this->resource->getStream(), + $this->getNumber(), + $partNumber, + \FT_UID | \FT_PEEK + ); + } + + /** + * Get an array of all parts for this message. + * + * @return PartInterface[] + */ + final public function getParts(): array + { + $this->lazyParseStructure(); + + return $this->parts; + } + + /** + * Get current child part. + * + * @return mixed + */ + final public function current() + { + $this->lazyParseStructure(); + + return $this->parts[$this->key]; + } + + /** + * Get current child part. + * + * @return mixed + */ + final public function getChildren() + { + return $this->current(); + } + + /** + * Get current child part. + * + * @return bool + */ + final public function hasChildren() + { + $this->lazyParseStructure(); + + return \count($this->parts) > 0; + } + + /** + * Get current part key. + * + * @return int + */ + final public function key() + { + return $this->key; + } + + /** + * Move to next part. + * + * @return int + */ + final public function next() + { + ++$this->key; + } + + /** + * Reset part key. + * + * @return int + */ + final public function rewind() + { + $this->key = 0; + } + + /** + * Check if current part is a valid one. + * + * @return bool + */ + final public function valid() + { + $this->lazyParseStructure(); + + return isset($this->parts[$this->key]); + } + + /** + * Parse part structure. + */ + private function lazyParseStructure(): void + { + if (true === $this->structureParsed) { + return; + } + $this->structureParsed = true; + + $this->lazyLoadStructure(); + + $this->type = self::$typesMap[$this->structure->type] ?? self::TYPE_UNKNOWN; + + // In our context, \ENCOTHER is as useful as an uknown encoding + $this->encoding = self::$encodingsMap[$this->structure->encoding] ?? self::ENCODING_UNKNOWN; + $this->subtype = $this->structure->subtype; + + foreach (['disposition', 'bytes', 'description'] as $optional) { + if (isset($this->structure->{$optional})) { + $this->{$optional} = $this->structure->{$optional}; + } + } + + $this->parameters = new Parameters(); + if ($this->structure->ifparameters) { + $this->parameters->add($this->structure->parameters); + } + + if ($this->structure->ifdparameters) { + $this->parameters->add($this->structure->dparameters); + } + + // When the message is not multipart and the body is the attachment content + // Prevents infinite recursion + if (self::isAttachment($this->structure) && !$this instanceof Attachment) { + $this->parts[] = new Attachment($this->resource, $this->getNumber(), '1', $this->structure); + } + + if (isset($this->structure->parts)) { + $parts = $this->structure->parts; + // https://secure.php.net/manual/en/function.imap-fetchbody.php#89002 + if ($this instanceof Attachment && $this->isEmbeddedMessage() && 1 === \count($parts) && \TYPEMULTIPART === $parts[0]->type) { + $parts = $parts[0]->parts; + } + foreach ($parts as $key => $partStructure) { + $partNumber = (!$this instanceof Message) ? $this->partNumber . '.' : ''; + $partNumber .= (string) ($key + 1); + + $newPartClass = self::isAttachment($partStructure) + ? Attachment::class + : SimplePart::class + ; + + $this->parts[] = new $newPartClass($this->resource, $this->getNumber(), $partNumber, $partStructure); + } + } + } + + /** + * Check if the given part is an attachment. + * + * @param \stdClass $part + * + * @return bool + */ + private static function isAttachment(\stdClass $part): bool + { + if (isset(self::$typesMap[$part->type]) && self::TYPE_MULTIPART === self::$typesMap[$part->type]) { + return false; + } + + // Attachment with correct Content-Disposition header + if ($part->ifdisposition) { + if ('attachment' === \strtolower($part->disposition)) { + return true; + } + + if ( + 'inline' === \strtolower($part->disposition) + && self::SUBTYPE_PLAIN !== \strtoupper($part->subtype) + && self::SUBTYPE_HTML !== \strtoupper($part->subtype) + ) { + return true; + } + } + + // Attachment without Content-Disposition header + if ($part->ifparameters) { + foreach ($part->parameters as $parameter) { + if (isset(self::$attachmentKeys[\strtolower($parameter->attribute)])) { + return true; + } + } + } + + /* + if ($part->ifdparameters) { + foreach ($part->dparameters as $parameter) { + if (isset(self::$attachmentKeys[\strtolower($parameter->attribute)])) { + return true; + } + } + } + */ + + return false; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php new file mode 100644 index 00000000..35655fdf --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php @@ -0,0 +1,64 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +use Ddeboer\Imap\Exception\NotEmbeddedMessageException; + +/** + * An e-mail attachment. + */ +final class Attachment extends AbstractPart implements AttachmentInterface +{ + /** + * Get attachment filename. + * + * @return null|string + */ + public function getFilename(): ?string + { + return $this->getParameters()->get('filename') + ?: $this->getParameters()->get('name'); + } + + /** + * Get attachment file size. + * + * @return int Number of bytes + */ + public function getSize() + { + return $this->getParameters()->get('size'); + } + + /** + * Is this attachment also an Embedded Message? + * + * @return bool + */ + public function isEmbeddedMessage(): bool + { + return self::TYPE_MESSAGE === $this->getType(); + } + + /** + * Return embedded message. + * + * @throws NotEmbeddedMessageException + * + * @return EmbeddedMessageInterface + */ + public function getEmbeddedMessage(): EmbeddedMessageInterface + { + if (!$this->isEmbeddedMessage()) { + throw new NotEmbeddedMessageException(\sprintf( + 'Attachment "%s" in message "%s" is not embedded message', + $this->getPartNumber(), + $this->getNumber() + )); + } + + return new EmbeddedMessage($this->resource, $this->getNumber(), $this->getPartNumber(), $this->getStructure()->parts[0]); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php new file mode 100644 index 00000000..595971b9 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php @@ -0,0 +1,39 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +/** + * An e-mail attachment. + */ +interface AttachmentInterface extends PartInterface +{ + /** + * Get attachment filename. + * + * @return null|string + */ + public function getFilename(): ?string; + + /** + * Get attachment file size. + * + * @return int Number of bytes + */ + public function getSize(); + + /** + * Is this attachment also an Embedded Message? + * + * @return bool + */ + public function isEmbeddedMessage(): bool; + + /** + * Return embedded message. + * + * @return EmbeddedMessageInterface + */ + public function getEmbeddedMessage(): EmbeddedMessageInterface; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php new file mode 100644 index 00000000..de830df6 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php @@ -0,0 +1,150 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +interface BasicMessageInterface extends PartInterface +{ + /** + * Get raw message headers. + * + * @return string + */ + public function getRawHeaders(): string; + + /** + * Get the raw message, including all headers, parts, etc. unencoded and unparsed. + * + * @return string the raw message + */ + public function getRawMessage(): string; + + /** + * Get message headers. + * + * @return Headers + */ + public function getHeaders(): Headers; + + /** + * Get message id. + * + * A unique message id in the form <...> + * + * @return null|string + */ + public function getId(): ?string; + + /** + * Get message sender (from headers). + * + * @return null|EmailAddress + */ + public function getFrom(): ?EmailAddress; + + /** + * Get To recipients. + * + * @return EmailAddress[] Empty array in case message has no To: recipients + */ + public function getTo(): array; + + /** + * Get Cc recipients. + * + * @return EmailAddress[] Empty array in case message has no CC: recipients + */ + public function getCc(): array; + + /** + * Get Bcc recipients. + * + * @return EmailAddress[] Empty array in case message has no BCC: recipients + */ + public function getBcc(): array; + + /** + * Get Reply-To recipients. + * + * @return EmailAddress[] Empty array in case message has no Reply-To: recipients + */ + public function getReplyTo(): array; + + /** + * Get Sender. + * + * @return EmailAddress[] Empty array in case message has no Sender: recipients + */ + public function getSender(): array; + + /** + * Get Return-Path. + * + * @return EmailAddress[] Empty array in case message has no Return-Path: recipients + */ + public function getReturnPath(): array; + + /** + * Get date (from headers). + * + * @return null|\DateTimeImmutable + */ + public function getDate(): ?\DateTimeImmutable; + + /** + * Get message size (from headers). + * + * @return null|int|string + */ + public function getSize(); + + /** + * Get message subject (from headers). + * + * @return null|string + */ + public function getSubject(): ?string; + + /** + * Get message In-Reply-To (from headers). + * + * @return array + */ + public function getInReplyTo(): array; + + /** + * Get message References (from headers). + * + * @return array + */ + public function getReferences(): array; + + /** + * Get body HTML. + * + * @return null|string Null if message has no HTML message part + */ + public function getBodyHtml(): ?string; + + /** + * Get body text. + * + * @return null|string + */ + public function getBodyText(): ?string; + + /** + * Get attachments (if any) linked to this e-mail. + * + * @return AttachmentInterface[] + */ + public function getAttachments(): array; + + /** + * Does this message have attachments? + * + * @return bool + */ + public function hasAttachments(): bool; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php new file mode 100644 index 00000000..3f3788a4 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php @@ -0,0 +1,94 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +/** + * An e-mail address. + */ +final class EmailAddress +{ + /** + * @var string + */ + private $mailbox; + + /** + * @var null|string + */ + private $hostname; + + /** + * @var null|string + */ + private $name; + + /** + * @var null|string + */ + private $address; + + /** + * @param string $mailbox + * @param null|string $hostname + * @param null|string $name + */ + public function __construct(string $mailbox, string $hostname = null, string $name = null) + { + $this->mailbox = $mailbox; + $this->hostname = $hostname; + $this->name = $name; + + if (null !== $hostname) { + $this->address = $mailbox . '@' . $hostname; + } + } + + /** + * @return null|string + */ + public function getAddress() + { + return $this->address; + } + + /** + * Returns address with person name. + * + * @return string + */ + public function getFullAddress(): string + { + $address = \sprintf('%s@%s', $this->mailbox, $this->hostname); + if (null !== $this->name) { + $address = \sprintf('"%s" <%s>', \addcslashes($this->name, '"'), $address); + } + + return $address; + } + + /** + * @return string + */ + public function getMailbox(): string + { + return $this->mailbox; + } + + /** + * @return null|string + */ + public function getHostname() + { + return $this->hostname; + } + + /** + * @return null|string + */ + public function getName() + { + return $this->name; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php new file mode 100644 index 00000000..1dfb8fd6 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php @@ -0,0 +1,81 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +final class EmbeddedMessage extends AbstractMessage implements EmbeddedMessageInterface +{ + /** + * @var null|Headers + */ + private $headers; + + /** + * @var null|string + */ + private $rawHeaders; + + /** + * @var null|string + */ + private $rawMessage; + + /** + * Get message headers. + * + * @return Headers + */ + public function getHeaders(): Headers + { + if (null === $this->headers) { + $this->headers = new Headers(\imap_rfc822_parse_headers($this->getRawHeaders())); + } + + return $this->headers; + } + + /** + * Get raw message headers. + * + * @return string + */ + public function getRawHeaders(): string + { + if (null === $this->rawHeaders) { + $rawHeaders = \explode("\r\n\r\n", $this->getRawMessage(), 2); + $this->rawHeaders = \current($rawHeaders); + } + + return $this->rawHeaders; + } + + /** + * Get the raw message, including all headers, parts, etc. unencoded and unparsed. + * + * @return string the raw message + */ + public function getRawMessage(): string + { + if (null === $this->rawMessage) { + $this->rawMessage = $this->doGetContent($this->getPartNumber()); + } + + return $this->rawMessage; + } + + /** + * Get content part number. + * + * @return string + */ + protected function getContentPartNumber(): string + { + $partNumber = $this->getPartNumber(); + if (0 === \count($this->getParts())) { + $partNumber .= '.1'; + } + + return $partNumber; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php new file mode 100644 index 00000000..c685edf9 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +interface EmbeddedMessageInterface extends BasicMessageInterface +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php new file mode 100644 index 00000000..f2848663 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php @@ -0,0 +1,76 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +/** + * Collection of message headers. + */ +final class Headers extends Parameters +{ + /** + * Constructor. + * + * @param \stdClass $headers + */ + public function __construct(\stdClass $headers) + { + parent::__construct(); + + // Store all headers as lowercase + $headers = \array_change_key_case((array) $headers); + + foreach ($headers as $key => $value) { + $this[$key] = $this->parseHeader($key, $value); + } + } + + /** + * Get header. + * + * @param string $key + * + * @return null|string + */ + public function get(string $key) + { + return parent::get(\strtolower($key)); + } + + /** + * Parse header. + * + * @param string $key + * @param mixed $value + * + * @return mixed + */ + private function parseHeader(string $key, $value) + { + switch ($key) { + case 'msgno': + return (int) $value; + case 'from': + case 'to': + case 'cc': + case 'bcc': + case 'reply_to': + case 'sender': + case 'return_path': + foreach ($value as $address) { + if (isset($address->mailbox)) { + $address->host = $address->host ?? null; + $address->personal = isset($address->personal) ? $this->decode($address->personal) : null; + } + } + + return $value; + case 'date': + case 'subject': + return $this->decode($value); + } + + return $value; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php new file mode 100644 index 00000000..bb2a66cb --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +class Parameters extends \ArrayIterator +{ + /** + * @var array + */ + private static $attachmentCustomKeys = [ + 'name*' => 'name', + 'filename*' => 'filename', + ]; + + /** + * @param array $parameters + */ + public function __construct(array $parameters = []) + { + parent::__construct(); + + $this->add($parameters); + } + + /** + * @param array $parameters + */ + public function add(array $parameters = []): void + { + foreach ($parameters as $parameter) { + $key = \strtolower($parameter->attribute); + if (isset(self::$attachmentCustomKeys[$key])) { + $key = self::$attachmentCustomKeys[$key]; + } + $value = $this->decode($parameter->value); + $this[$key] = $value; + } + } + + /** + * @param string $key + * + * @return mixed + */ + public function get(string $key) + { + return $this[$key] ?? null; + } + + /** + * Decode value. + * + * @param string $value + * + * @return string + */ + final protected function decode(string $value): string + { + $parts = \imap_mime_header_decode($value); + if (!\is_array($parts)) { + return $value; + } + + $decoded = ''; + foreach ($parts as $part) { + $text = $part->text; + if ('default' !== $part->charset) { + $text = Transcoder::decode($text, $part->charset); + } + // RFC2231 + if (1 === \preg_match('/^(?<encoding>[^\']+)\'[^\']*?\'(?<urltext>.+)$/', $text, $matches)) { + $hasInvalidChars = \preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $matches['urltext']); + $hasEscapedChars = \preg_match('#%[a-zA-Z0-9]{2}#', $matches['urltext']); + if (!$hasInvalidChars && $hasEscapedChars) { + $text = Transcoder::decode(\urldecode($matches['urltext']), $matches['encoding']); + } + } + + $decoded .= $text; + } + + return $decoded; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php new file mode 100644 index 00000000..d5f63699 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php @@ -0,0 +1,130 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +/** + * A message part. + */ +interface PartInterface extends \RecursiveIterator +{ + const TYPE_TEXT = 'text'; + const TYPE_MULTIPART = 'multipart'; + const TYPE_MESSAGE = 'message'; + const TYPE_APPLICATION = 'application'; + const TYPE_AUDIO = 'audio'; + const TYPE_IMAGE = 'image'; + const TYPE_VIDEO = 'video'; + const TYPE_MODEL = 'model'; + const TYPE_OTHER = 'other'; + const TYPE_UNKNOWN = 'unknown'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BINARY = 'binary'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + const ENCODING_UNKNOWN = 'unknown'; + + const SUBTYPE_PLAIN = 'PLAIN'; + const SUBTYPE_HTML = 'HTML'; + + /** + * Get message number (from headers). + * + * @return int + */ + public function getNumber(): int; + + /** + * Part charset. + * + * @return null|string + */ + public function getCharset(): ?string; + + /** + * Part type. + * + * @return null|string + */ + public function getType(): ?string; + + /** + * Part subtype. + * + * @return null|string + */ + public function getSubtype(): ?string; + + /** + * Part encoding. + * + * @return null|string + */ + public function getEncoding(): ?string; + + /** + * Part disposition. + * + * @return null|string + */ + public function getDisposition(): ?string; + + /** + * Part bytes. + * + * @return null|int|string + */ + public function getBytes(); + + /** + * Part lines. + * + * @return null|string + */ + public function getLines(): ?string; + + /** + * Part parameters. + * + * @return Parameters + */ + public function getParameters(): Parameters; + + /** + * Get raw part content. + * + * @return string + */ + public function getContent(): string; + + /** + * Get decoded part content. + * + * @return string + */ + public function getDecodedContent(): string; + + /** + * Part structure. + * + * @return \stdClass + */ + public function getStructure(): \stdClass; + + /** + * Get part number. + * + * @return string + */ + public function getPartNumber(): string; + + /** + * Get an array of all parts for this message. + * + * @return PartInterface[] + */ + public function getParts(): array; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/SimplePart.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/SimplePart.php new file mode 100644 index 00000000..3c6188d7 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/SimplePart.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +/** + * A message part. + */ +final class SimplePart extends AbstractPart +{ +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php new file mode 100644 index 00000000..28daec13 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php @@ -0,0 +1,326 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Message; + +use Ddeboer\Imap\Exception\UnsupportedCharsetException; + +final class Transcoder +{ + /** + * @var array + * + * @see https://encoding.spec.whatwg.org/#encodings + * @see https://dxr.mozilla.org/mozilla-central/source/dom/encoding/labelsencodings.properties + * @see https://dxr.mozilla.org/mozilla1.9.1/source/intl/uconv/src/charsetalias.properties + * @see https://msdn.microsoft.com/en-us/library/cc194829.aspx + */ + private static $charsetAliases = [ + '128' => 'Shift_JIS', + '129' => 'EUC-KR', + '134' => 'GB2312', + '136' => 'Big5', + '161' => 'windows-1253', + '162' => 'windows-1254', + '177' => 'windows-1255', + '178' => 'windows-1256', + '186' => 'windows-1257', + '204' => 'windows-1251', + '222' => 'windows-874', + '238' => 'windows-1250', + '5601' => 'EUC-KR', + '646' => 'us-ascii', + '850' => 'IBM850', + '852' => 'IBM852', + '855' => 'IBM855', + '857' => 'IBM857', + '862' => 'IBM862', + '864' => 'IBM864', + '864i' => 'IBM864i', + '866' => 'IBM866', + 'ansi-1251' => 'windows-1251', + 'ansi_x3.4-1968' => 'us-ascii', + 'arabic' => 'ISO-8859-6', + 'ascii' => 'us-ascii', + 'asmo-708' => 'ISO-8859-6', + 'big5-hkscs' => 'Big5', + 'chinese' => 'GB2312', + 'cn-big5' => 'Big5', + 'cns11643' => 'x-euc-tw', + 'cp-866' => 'IBM866', + 'cp1250' => 'windows-1250', + 'cp1251' => 'windows-1251', + 'cp1252' => 'windows-1252', + 'cp1253' => 'windows-1253', + 'cp1254' => 'windows-1254', + 'cp1255' => 'windows-1255', + 'cp1256' => 'windows-1256', + 'cp1257' => 'windows-1257', + 'cp1258' => 'windows-1258', + 'cp819' => 'ISO-8859-1', + 'cp850' => 'IBM850', + 'cp852' => 'IBM852', + 'cp855' => 'IBM855', + 'cp857' => 'IBM857', + 'cp862' => 'IBM862', + 'cp864' => 'IBM864', + 'cp864i' => 'IBM864i', + 'cp866' => 'IBM866', + 'cp932' => 'Shift_JIS', + 'csbig5' => 'Big5', + 'cseucjpkdfmtjapanese' => 'EUC-JP', + 'cseuckr' => 'EUC-KR', + 'cseucpkdfmtjapanese' => 'EUC-JP', + 'csgb2312' => 'GB2312', + 'csibm850' => 'IBM850', + 'csibm852' => 'IBM852', + 'csibm855' => 'IBM855', + 'csibm857' => 'IBM857', + 'csibm862' => 'IBM862', + 'csibm864' => 'IBM864', + 'csibm864i' => 'IBM864i', + 'csibm866' => 'IBM866', + 'csiso103t618bit' => 'T.61-8bit', + 'csiso111ecmacyrillic' => 'ISO-IR-111', + 'csiso2022jp' => 'ISO-2022-JP', + 'csiso2022jp2' => 'ISO-2022-JP', + 'csiso2022kr' => 'ISO-2022-KR', + 'csiso58gb231280' => 'GB2312', + 'csiso88596e' => 'ISO-8859-6-E', + 'csiso88596i' => 'ISO-8859-6-I', + 'csiso88598e' => 'ISO-8859-8-E', + 'csiso88598i' => 'ISO-8859-8-I', + 'csisolatin1' => 'ISO-8859-1', + 'csisolatin2' => 'ISO-8859-2', + 'csisolatin3' => 'ISO-8859-3', + 'csisolatin4' => 'ISO-8859-4', + 'csisolatin5' => 'ISO-8859-9', + 'csisolatin6' => 'ISO-8859-10', + 'csisolatin9' => 'ISO-8859-15', + 'csisolatinarabic' => 'ISO-8859-6', + 'csisolatincyrillic' => 'ISO-8859-5', + 'csisolatingreek' => 'ISO-8859-7', + 'csisolatinhebrew' => 'ISO-8859-8', + 'cskoi8r' => 'KOI8-R', + 'csksc56011987' => 'EUC-KR', + 'csmacintosh' => 'x-mac-roman', + 'csshiftjis' => 'Shift_JIS', + 'csueckr' => 'EUC-KR', + 'csunicode' => 'UTF-16BE', + 'csunicode11' => 'UTF-16BE', + 'csunicode11utf7' => 'UTF-7', + 'csunicodeascii' => 'UTF-16BE', + 'csunicodelatin1' => 'UTF-16BE', + 'csviqr' => 'VIQR', + 'csviscii' => 'VISCII', + 'cyrillic' => 'ISO-8859-5', + 'dos-874' => 'windows-874', + 'ecma-114' => 'ISO-8859-6', + 'ecma-118' => 'ISO-8859-7', + 'ecma-cyrillic' => 'ISO-IR-111', + 'elot_928' => 'ISO-8859-7', + 'gb_2312' => 'GB2312', + 'gb_2312-80' => 'GB2312', + 'gbk' => 'x-gbk', + 'greek' => 'ISO-8859-7', + 'greek8' => 'ISO-8859-7', + 'hebrew' => 'ISO-8859-8', + 'ibm-864' => 'IBM864', + 'ibm-864i' => 'IBM864i', + 'ibm819' => 'ISO-8859-1', + 'ibm874' => 'windows-874', + 'iso-10646' => 'UTF-16BE', + 'iso-10646-j-1' => 'UTF-16BE', + 'iso-10646-ucs-2' => 'UTF-16BE', + 'iso-10646-ucs-4' => 'UTF-32BE', + 'iso-10646-ucs-basic' => 'UTF-16BE', + 'iso-10646-unicode-latin1' => 'UTF-16BE', + 'iso-2022-cn-ext' => 'ISO-2022-CN', + 'iso-2022-jp-2' => 'ISO-2022-JP', + 'iso-8859-8i' => 'ISO-8859-8-I', + 'iso-ir-100' => 'ISO-8859-1', + 'iso-ir-101' => 'ISO-8859-2', + 'iso-ir-103' => 'T.61-8bit', + 'iso-ir-109' => 'ISO-8859-3', + 'iso-ir-110' => 'ISO-8859-4', + 'iso-ir-126' => 'ISO-8859-7', + 'iso-ir-127' => 'ISO-8859-6', + 'iso-ir-138' => 'ISO-8859-8', + 'iso-ir-144' => 'ISO-8859-5', + 'iso-ir-148' => 'ISO-8859-9', + 'iso-ir-149' => 'EUC-KR', + 'iso-ir-157' => 'ISO-8859-10', + 'iso-ir-58' => 'GB2312', + 'iso8859-1' => 'ISO-8859-1', + 'iso8859-10' => 'ISO-8859-10', + 'iso8859-11' => 'ISO-8859-11', + 'iso8859-13' => 'ISO-8859-13', + 'iso8859-14' => 'ISO-8859-14', + 'iso8859-15' => 'ISO-8859-15', + 'iso8859-2' => 'ISO-8859-2', + 'iso8859-3' => 'ISO-8859-3', + 'iso8859-4' => 'ISO-8859-4', + 'iso8859-5' => 'ISO-8859-5', + 'iso8859-6' => 'ISO-8859-6', + 'iso8859-7' => 'ISO-8859-7', + 'iso8859-8' => 'ISO-8859-8', + 'iso8859-9' => 'ISO-8859-9', + 'iso88591' => 'ISO-8859-1', + 'iso885910' => 'ISO-8859-10', + 'iso885911' => 'ISO-8859-11', + 'iso885912' => 'ISO-8859-12', + 'iso885913' => 'ISO-8859-13', + 'iso885914' => 'ISO-8859-14', + 'iso885915' => 'ISO-8859-15', + 'iso88592' => 'ISO-8859-2', + 'iso88593' => 'ISO-8859-3', + 'iso88594' => 'ISO-8859-4', + 'iso88595' => 'ISO-8859-5', + 'iso88596' => 'ISO-8859-6', + 'iso88597' => 'ISO-8859-7', + 'iso88598' => 'ISO-8859-8', + 'iso88599' => 'ISO-8859-9', + 'iso_8859-1' => 'ISO-8859-1', + 'iso_8859-15' => 'ISO-8859-15', + 'iso_8859-1:1987' => 'ISO-8859-1', + 'iso_8859-2' => 'ISO-8859-2', + 'iso_8859-2:1987' => 'ISO-8859-2', + 'iso_8859-3' => 'ISO-8859-3', + 'iso_8859-3:1988' => 'ISO-8859-3', + 'iso_8859-4' => 'ISO-8859-4', + 'iso_8859-4:1988' => 'ISO-8859-4', + 'iso_8859-5' => 'ISO-8859-5', + 'iso_8859-5:1988' => 'ISO-8859-5', + 'iso_8859-6' => 'ISO-8859-6', + 'iso_8859-6:1987' => 'ISO-8859-6', + 'iso_8859-7' => 'ISO-8859-7', + 'iso_8859-7:1987' => 'ISO-8859-7', + 'iso_8859-8' => 'ISO-8859-8', + 'iso_8859-8:1988' => 'ISO-8859-8', + 'iso_8859-9' => 'ISO-8859-9', + 'iso_8859-9:1989' => 'ISO-8859-9', + 'koi' => 'KOI8-R', + 'koi8' => 'KOI8-R', + 'koi8-ru' => 'KOI8-U', + 'koi8_r' => 'KOI8-R', + 'korean' => 'EUC-KR', + 'ks_c_5601-1987' => 'EUC-KR', + 'ks_c_5601-1989' => 'EUC-KR', + 'ksc5601' => 'EUC-KR', + 'ksc_5601' => 'EUC-KR', + 'l1' => 'ISO-8859-1', + 'l2' => 'ISO-8859-2', + 'l3' => 'ISO-8859-3', + 'l4' => 'ISO-8859-4', + 'l5' => 'ISO-8859-9', + 'l6' => 'ISO-8859-10', + 'l9' => 'ISO-8859-15', + 'latin1' => 'ISO-8859-1', + 'latin2' => 'ISO-8859-2', + 'latin3' => 'ISO-8859-3', + 'latin4' => 'ISO-8859-4', + 'latin5' => 'ISO-8859-9', + 'latin6' => 'ISO-8859-10', + 'logical' => 'ISO-8859-8-I', + 'mac' => 'x-mac-roman', + 'macintosh' => 'x-mac-roman', + 'ms932' => 'Shift_JIS', + 'ms_kanji' => 'Shift_JIS', + 'shift-jis' => 'Shift_JIS', + 'sjis' => 'Shift_JIS', + 'sun_eu_greek' => 'ISO-8859-7', + 't.61' => 'T.61-8bit', + 'tis620' => 'TIS-620', + 'unicode-1-1-utf-7' => 'UTF-7', + 'unicode-1-1-utf-8' => 'UTF-8', + 'unicode-2-0-utf-7' => 'UTF-7', + 'visual' => 'ISO-8859-8', + 'windows-31j' => 'Shift_JIS', + 'windows-949' => 'EUC-KR', + 'x-cp1250' => 'windows-1250', + 'x-cp1251' => 'windows-1251', + 'x-cp1252' => 'windows-1252', + 'x-cp1253' => 'windows-1253', + 'x-cp1254' => 'windows-1254', + 'x-cp1255' => 'windows-1255', + 'x-cp1256' => 'windows-1256', + 'x-cp1257' => 'windows-1257', + 'x-cp1258' => 'windows-1258', + 'x-euc-jp' => 'EUC-JP', + 'x-iso-10646-ucs-2-be' => 'UTF-16BE', + 'x-iso-10646-ucs-2-le' => 'UTF-16LE', + 'x-iso-10646-ucs-4-be' => 'UTF-32BE', + 'x-iso-10646-ucs-4-le' => 'UTF-32LE', + 'x-sjis' => 'Shift_JIS', + 'x-unicode-2-0-utf-7' => 'UTF-7', + 'x-x-big5' => 'Big5', + 'zh_cn.euc' => 'GB2312', + 'zh_tw-big5' => 'Big5', + 'zh_tw-euc' => 'x-euc-tw', + ]; + + /** + * Decode text to UTF-8. + * + * @param string $text Text to decode + * @param string $fromCharset Original charset + * + * @return string + */ + public static function decode(string $text, string $fromCharset): string + { + static $utf8Aliases = [ + 'unicode-1-1-utf-8' => true, + 'utf8' => true, + 'utf-8' => true, + 'UTF8' => true, + 'UTF-8' => true, + ]; + + if (isset($utf8Aliases[$fromCharset])) { + return $text; + } + + $originalFromCharset = $fromCharset; + $lowercaseFromCharset = \strtolower($fromCharset); + if (isset(self::$charsetAliases[$lowercaseFromCharset])) { + $fromCharset = self::$charsetAliases[$lowercaseFromCharset]; + } + + \set_error_handler(function () {}); + + $iconvDecodedText = \iconv($fromCharset, 'UTF-8', $text); + if (false === $iconvDecodedText) { + $iconvDecodedText = \iconv($originalFromCharset, 'UTF-8', $text); + } + + \restore_error_handler(); + + if (false !== $iconvDecodedText) { + return $iconvDecodedText; + } + + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorMessage = $message; + $errorNumber = $nr; + }); + + $decodedText = \mb_convert_encoding($text, 'UTF-8', $fromCharset); + + \restore_error_handler(); + + if (null !== $errorMessage) { + throw new UnsupportedCharsetException(\sprintf( + 'Unsupported charset "%s"%s: %s', + $originalFromCharset, + ($fromCharset !== $originalFromCharset) ? \sprintf(' (alias found: "%s")', $fromCharset) : '', + $errorMessage + ), $errorNumber); + } + + return $decodedText; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php new file mode 100644 index 00000000..d14ce716 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php @@ -0,0 +1,120 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +/** + * An IMAP message (e-mail). + */ +interface MessageInterface extends Message\BasicMessageInterface +{ + /** + * Get raw part content. + * + * @return string + */ + public function getContent(): string; + + /** + * Get message recent flag value (from headers). + * + * @return null|string + */ + public function isRecent(): ?string; + + /** + * Get message unseen flag value (from headers). + * + * @return bool + */ + public function isUnseen(): bool; + + /** + * Get message flagged flag value (from headers). + * + * @return bool + */ + public function isFlagged(): bool; + + /** + * Get message answered flag value (from headers). + * + * @return bool + */ + public function isAnswered(): bool; + + /** + * Get message deleted flag value (from headers). + * + * @return bool + */ + public function isDeleted(): bool; + + /** + * Get message draft flag value (from headers). + * + * @return bool + */ + public function isDraft(): bool; + + /** + * Has the message been marked as read? + * + * @return bool + */ + public function isSeen(): bool; + + /** + * Mark message as seen. + * + * @return bool + * + * @deprecated since version 1.1, to be removed in 2.0 + */ + public function maskAsSeen(): bool; + + /** + * Mark message as seen. + * + * @return bool + */ + public function markAsSeen(): bool; + + /** + * Move message to another mailbox. + * + * @param MailboxInterface $mailbox + */ + public function copy(MailboxInterface $mailbox): void; + + /** + * Move message to another mailbox. + * + * @param MailboxInterface $mailbox + */ + public function move(MailboxInterface $mailbox): void; + + /** + * Delete message. + */ + public function delete(): void; + + /** + * Set Flag Message. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * + * @return bool + */ + public function setFlag(string $flag): bool; + + /** + * Clear Flag Message. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * + * @return bool + */ + public function clearFlag(string $flag): bool; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIterator.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIterator.php new file mode 100644 index 00000000..024461b9 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIterator.php @@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +final class MessageIterator extends \ArrayIterator implements MessageIteratorInterface +{ + /** + * @var ImapResourceInterface + */ + private $resource; + + /** + * Constructor. + * + * @param ImapResourceInterface $resource IMAP resource + * @param array $messageNumbers Array of message numbers + */ + public function __construct(ImapResourceInterface $resource, array $messageNumbers) + { + $this->resource = $resource; + + parent::__construct($messageNumbers); + } + + /** + * Get current message. + * + * @return MessageInterface + */ + public function current(): MessageInterface + { + $current = parent::current(); + if (!\is_int($current)) { + throw new Exception\OutOfBoundsException(\sprintf( + 'The current value "%s" isn\'t an integer and doesn\'t represent a message;' + . ' try to cycle this "%s" with a native php function like foreach or with the method getArrayCopy(),' + . ' or check it by calling the methods valid().', + \is_object($current) ? \get_class($current) : \gettype($current), + static::class + )); + } + + return new Message($this->resource, $current); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php new file mode 100644 index 00000000..f1b9fd92 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +interface MessageIteratorInterface extends \Iterator +{ + /** + * Get current message. + * + * @return MessageInterface + */ + public function current(): MessageInterface; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php new file mode 100644 index 00000000..0e925954 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search; + +use DateTimeInterface; + +/** + * Represents a date condition. + */ +abstract class AbstractDate implements ConditionInterface +{ + /** + * Format for dates to be sent to the IMAP server. + * + * @var string + */ + private $dateFormat; + + /** + * The date to be used for the condition. + * + * @var DateTimeInterface + */ + private $date; + + /** + * Constructor. + * + * @param DateTimeInterface $date optional date for the condition + */ + public function __construct(DateTimeInterface $date, string $dateFormat = 'j-M-Y') + { + $this->date = $date; + $this->dateFormat = $dateFormat; + } + + /** + * Converts the condition to a string that can be sent to the IMAP server. + * + * @return string + */ + final public function toString(): string + { + return \sprintf('%s "%s"', $this->getKeyword(), $this->date->format($this->dateFormat)); + } + + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + abstract protected function getKeyword(): string; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php new file mode 100644 index 00000000..833ea3c3 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search; + +/** + * Represents a text based condition. Text based conditions use a contains + * restriction. + */ +abstract class AbstractText implements ConditionInterface +{ + /** + * Text to be used for the condition. + * + * @var string + */ + private $text; + + /** + * Constructor. + * + * @param string $text optional text for the condition + */ + public function __construct(string $text) + { + $this->text = $text; + } + + /** + * Converts the condition to a string that can be sent to the IMAP server. + * + * @return string + */ + final public function toString(): string + { + return \sprintf('%s "%s"', $this->getKeyword(), $this->text); + } + + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + abstract protected function getKeyword(): string; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php new file mode 100644 index 00000000..e4c0a97d --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search; + +/** + * Represents a condition that can be used in a search expression. + */ +interface ConditionInterface +{ + /** + * Converts the condition to a string that can be sent to the IMAP server. + * + * @return string + */ + public function toString(): string; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/Before.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/Before.php new file mode 100644 index 00000000..266c683b --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/Before.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Date; + +use Ddeboer\Imap\Search\AbstractDate; + +/** + * Represents a date before condition. Messages must have a date before the + * specified date in order to match the condition. + */ +final class Before extends AbstractDate +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'BEFORE'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/On.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/On.php new file mode 100644 index 00000000..42e6408f --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/On.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Date; + +use Ddeboer\Imap\Search\AbstractDate; + +/** + * Represents a date on condition. Messages must have a date matching the + * specified date in order to match the condition. + */ +final class On extends AbstractDate +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'ON'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/Since.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/Since.php new file mode 100644 index 00000000..2d17b454 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Date/Since.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Date; + +use Ddeboer\Imap\Search\AbstractDate; + +/** + * Represents a date after condition. Messages must have a date after the + * specified date in order to match the condition. + */ +final class Since extends AbstractDate +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'SINCE'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/Bcc.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/Bcc.php new file mode 100644 index 00000000..32ceff03 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/Bcc.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Email; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a "Bcc" email address condition. Messages must have been addressed + * to the specified recipient (along with any others) in order to match the + * condition. + */ +final class Bcc extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'BCC'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/Cc.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/Cc.php new file mode 100644 index 00000000..57edbc07 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/Cc.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Email; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a "Cc" email address condition. Messages must have been addressed + * to the specified recipient (along with any others) in order to match the + * condition. + */ +final class Cc extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'CC'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/From.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/From.php new file mode 100644 index 00000000..f4803e6c --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/From.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Email; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a "From" email address condition. Messages must have been sent + * from the specified email address in order to match the condition. + */ +final class From extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'FROM'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/To.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/To.php new file mode 100644 index 00000000..a3091987 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Email/To.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Email; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a "To" email address condition. Messages must have been addressed + * to the specified recipient (along with any others) in order to match the + * condition. + */ +final class To extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'TO'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Answered.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Answered.php new file mode 100644 index 00000000..8469312e --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Answered.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Flag; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents an ANSWERED flag condition. Messages must have the \\ANSWERED flag + * set in order to match the condition. + */ +final class Answered implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'ANSWERED'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Flagged.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Flagged.php new file mode 100644 index 00000000..a75a5ad8 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Flagged.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Flag; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents a FLAGGED flag condition. Messages must have the \\FLAGGED flag + * (i.e. urgent or important) set in order to match the condition. + */ +final class Flagged implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'FLAGGED'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Recent.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Recent.php new file mode 100644 index 00000000..2407e24c --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Recent.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Flag; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents an RECENT flag condition. Messages must have the \\RECENT flag + * set in order to match the condition. + */ +final class Recent implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'RECENT'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Seen.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Seen.php new file mode 100644 index 00000000..4e14a260 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Seen.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Flag; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents an SEEN flag condition. Messages must have the \\SEEN flag + * set in order to match the condition. + */ +final class Seen implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'SEEN'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unanswered.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unanswered.php new file mode 100644 index 00000000..03c44f21 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unanswered.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Flag; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents an UNANSWERED flag condition. Messages must not have the + * \\ANSWERED flag set in order to match the condition. + */ +final class Unanswered implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'UNANSWERED'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unflagged.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unflagged.php new file mode 100644 index 00000000..c9ea4267 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unflagged.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Flag; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents a UNFLAGGED flag condition. Messages must no have the \\FLAGGED + * flag (i.e. urgent or important) set in order to match the condition. + */ +final class Unflagged implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'UNFLAGGED'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unseen.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unseen.php new file mode 100644 index 00000000..11717faa --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Flag/Unseen.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Flag; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents an UNSEEN flag condition. Messages must not have the \\SEEN flag + * set in order to match the condition. + */ +final class Unseen implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'UNSEEN'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/All.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/All.php new file mode 100644 index 00000000..f71cc7b0 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/All.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\LogicalOperator; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents an ALL operator. Messages must match all conditions following this + * operator in order to match the expression. + */ +final class All implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'ALL'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/OrConditions.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/OrConditions.php new file mode 100644 index 00000000..7d522257 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/OrConditions.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\LogicalOperator; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents an OR operator. Messages only need to match one of the conditions + * after this operator to match the expression. + */ +final class OrConditions implements ConditionInterface +{ + /** + * The conditions that together represent the expression. + * + * @var array + */ + private $conditions = []; + + public function __construct(array $conditions) + { + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + } + + /** + * Adds a new condition to the expression. + * + * @param ConditionInterface $condition the condition to be added + */ + private function addCondition(ConditionInterface $condition) + { + $this->conditions[] = $condition; + } + + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + $conditions = \array_map(function (ConditionInterface $condition) { + return $condition->toString(); + }, $this->conditions); + + return \sprintf('( %s )', \implode(' OR ', $conditions)); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php new file mode 100644 index 00000000..72da06db --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search; + +/** + * Represents a raw expression. + */ +final class RawExpression implements ConditionInterface +{ + /** + * Text to be used for the condition. + * + * @var string + */ + private $expression; + + /** + * @param string $expression text for the condition + */ + public function __construct(string $expression) + { + $this->expression = $expression; + } + + /** + * @return string + */ + public function toString(): string + { + return $this->expression; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php new file mode 100644 index 00000000..90ec5579 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\State; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents a DELETED condition. Messages must have been marked for deletion + * but not yet expunged in order to match the condition. + */ +final class Deleted implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'DELETED'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/NewMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/NewMessage.php new file mode 100644 index 00000000..4634ba17 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/NewMessage.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\State; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents a NEW condition. Only new messages will match this condition. + */ +final class NewMessage implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'NEW'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Old.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Old.php new file mode 100644 index 00000000..77fdff81 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Old.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\State; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents an OLD condition. Only old messages will match this condition. + */ +final class Old implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'OLD'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Undeleted.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Undeleted.php new file mode 100644 index 00000000..049f712a --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Undeleted.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\State; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Represents a UNDELETED condition. Messages must not have been marked for + * deletion in order to match the condition. + */ +final class Undeleted implements ConditionInterface +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + return 'UNDELETED'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Body.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Body.php new file mode 100644 index 00000000..c2e9929e --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Body.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Text; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a body text contains condition. Messages must have a body + * containing the specified text in order to match the condition. + */ +final class Body extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'BODY'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Keyword.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Keyword.php new file mode 100644 index 00000000..e319b02c --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Keyword.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Text; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a keyword text contains condition. Messages must have a keyword + * matching the specified text in order to match the condition. + */ +final class Keyword extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'KEYWORD'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Subject.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Subject.php new file mode 100644 index 00000000..4fc96009 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Subject.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Text; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a subject contains condition. Messages must have a subject + * containing the specified text in order to match the condition. + */ +final class Subject extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'SUBJECT'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Text.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Text.php new file mode 100644 index 00000000..eea8a5fc --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Text.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Text; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a message text contains condition. Messages must contain the + * specified text in order to match the condition. + */ +final class Text extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'TEXT'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Unkeyword.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Unkeyword.php new file mode 100644 index 00000000..48235401 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/Text/Unkeyword.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Search\Text; + +use Ddeboer\Imap\Search\AbstractText; + +/** + * Represents a keyword text does not contain condition. Messages must not have + * a keyword matching the specified text in order to match the condition. + */ +final class Unkeyword extends AbstractText +{ + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + protected function getKeyword(): string + { + return 'UNKEYWORD'; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/SearchExpression.php b/data/web/inc/lib/vendor/ddeboer/imap/src/SearchExpression.php new file mode 100644 index 00000000..d58d658f --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/SearchExpression.php @@ -0,0 +1,48 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +use Ddeboer\Imap\Search\ConditionInterface; + +/** + * Defines a search expression that can be used to look up email messages. + */ +final class SearchExpression implements ConditionInterface +{ + /** + * The conditions that together represent the expression. + * + * @var array + */ + private $conditions = []; + + /** + * Adds a new condition to the expression. + * + * @param ConditionInterface $condition the condition to be added + * + * @return self + */ + public function addCondition(ConditionInterface $condition): self + { + $this->conditions[] = $condition; + + return $this; + } + + /** + * Converts the expression to a string that can be sent to the IMAP server. + * + * @return string + */ + public function toString(): string + { + $conditions = \array_map(function (ConditionInterface $condition) { + return $condition->toString(); + }, $this->conditions); + + return \implode(' ', $conditions); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php new file mode 100644 index 00000000..361ca2aa --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php @@ -0,0 +1,138 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +use Ddeboer\Imap\Exception\AuthenticationFailedException; + +/** + * An IMAP server. + */ +final class Server implements ServerInterface +{ + /** + * @var string Internet domain name or bracketed IP address of server + */ + private $hostname; + + /** + * @var string TCP port number + */ + private $port; + + /** + * @var string Optional flags + */ + private $flags; + + /** + * @var array + */ + private $parameters; + + /** + * @var int Connection options + */ + private $options; + + /** + * @var int Retries number + */ + private $retries; + + /** + * Constructor. + * + * @param string $hostname Internet domain name or bracketed IP address + * of server + * @param string $port TCP port number + * @param string $flags Optional flags + * @param array $parameters Connection parameters + * @param int $options Connection options + * @param int $retries Retries number + */ + public function __construct( + string $hostname, + string $port = '993', + string $flags = '/imap/ssl/validate-cert', + array $parameters = [], + int $options = 0, + int $retries = 1 + ) { + if (!\function_exists('imap_open')) { + throw new \RuntimeException('IMAP extension must be enabled'); + } + + $this->hostname = $hostname; + $this->port = $port; + $this->flags = $flags ? '/' . \ltrim($flags, '/') : ''; + $this->parameters = $parameters; + $this->options = $options; + $this->retries = $retries; + } + + /** + * Authenticate connection. + * + * @param string $username Username + * @param string $password Password + * + * @throws AuthenticationFailedException + * + * @return ConnectionInterface + */ + public function authenticate(string $username, string $password): ConnectionInterface + { + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorMessage = $message; + $errorNumber = $nr; + }); + + $resource = \imap_open( + $this->getServerString(), + $username, + $password, + $this->options, + $this->retries, + $this->parameters + ); + + \restore_error_handler(); + + if (false === $resource || null !== $errorMessage) { + throw new AuthenticationFailedException(\sprintf( + 'Authentication failed for user "%s"%s', + $username, + null !== $errorMessage ? ': ' . $errorMessage : '' + ), $errorNumber); + } + + $check = \imap_check($resource); + $mailbox = $check->Mailbox; + $connection = \substr($mailbox, 0, \strpos($mailbox, '}') + 1); + + // These are necessary to get rid of PHP throwing IMAP errors + \imap_errors(); + \imap_alerts(); + + return new Connection(new ImapResource($resource), $connection); + } + + /** + * Glues hostname, port and flags and returns result. + * + * @return string + */ + private function getServerString(): string + { + return \sprintf( + '{%s%s%s}', + $this->hostname, + '' !== $this->port ? ':' . $this->port : '', + $this->flags + ); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php new file mode 100644 index 00000000..f67dd6c8 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap; + +/** + * An IMAP server. + */ +interface ServerInterface +{ + /** + * Authenticate connection. + * + * @param string $username Username + * @param string $password Password + * + * @return ConnectionInterface + */ + public function authenticate(string $username, string $password): ConnectionInterface; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Test/RawMessageIterator.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Test/RawMessageIterator.php new file mode 100644 index 00000000..08500b64 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Test/RawMessageIterator.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Ddeboer\Imap\Test; + +use Ddeboer\Imap\MessageInterface; +use Ddeboer\Imap\MessageIteratorInterface; + +/** + * A MessageIterator to be used in a mocked environment. + */ +final class RawMessageIterator extends \ArrayIterator implements MessageIteratorInterface +{ + public function current(): MessageInterface + { + return parent::current(); + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md b/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md new file mode 100644 index 00000000..226cf976 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# How to contribute + + +## Issues + +When [filing bugs](https://github.com/matthiasmullie/minify/issues/new), +try to be as thorough as possible: +* What version did you use? +* What did you try to do? ***Please post the relevant parts of your code.*** +* What went wrong? ***Please include error messages, if any.*** +* What was the expected result? + + +## Pull requests + +Bug fixes and general improvements to the existing codebase are always welcome. +New features are also welcome, but will be judged on an individual basis. If +you'd rather not risk wasting your time implementing a new feature only to see +it turned down, please start the discussion by +[opening an issue](https://github.com/matthiasmullie/minify/issues/new). + +Don't forget to add your changes to the [changelog](CHANGELOG.md). + + +### Testing + +Please include tests for every change or addition to the code. +To run the complete test suite: + +```sh +vendor/bin/phpunit +``` + +When submitting a new pull request, please make sure that that the test suite +passes (Travis CI will run it & report back on your pull request.) + +To run the tests on Windows, run `tests/convert_symlinks_to_windows_style.sh` +from the command line in order to convert Linux-style test symlinks to +Windows-style. + + +### Coding standards + +All code must follow [PSR-2](http://www.php-fig.org/psr/psr-2/). Just make sure +to run php-cs-fixer before submitting the code, it'll take care of the +formatting for you: + +```sh +vendor/bin/php-cs-fixer fix src +vendor/bin/php-cs-fixer fix tests +``` + +Document the code thoroughly! + + +## License + +Note that minify is MIT-licensed, which basically allows anyone to do +anything they like with it, without restriction. diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile b/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile new file mode 100644 index 00000000..d17f9d74 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile @@ -0,0 +1,13 @@ +ARG version=cli +FROM php:$version + +COPY . /var/www +WORKDIR /var/www + +RUN apt-get update +RUN apt-get install -y zip unzip zlib1g-dev +RUN docker-php-ext-install zip +RUN docker-php-ext-install pcntl +RUN curl -sS https://getcomposer.org/installer | php +RUN mv composer.phar /usr/local/bin/composer +RUN composer install diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/LICENSE b/data/web/inc/lib/vendor/matthiasmullie/minify/LICENSE new file mode 100644 index 00000000..0c0d08a7 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2012 Matthias Mullie + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifycss b/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifycss new file mode 100755 index 00000000..6a681a85 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifycss @@ -0,0 +1,45 @@ +#!/usr/bin/env php +<?php +use MatthiasMullie\Minify; + +// command line utility to minify CSS +if (file_exists(__DIR__ . '/../../../autoload.php')) { + // if composer install + require_once __DIR__ . '/../../../autoload.php'; +} else { + require_once __DIR__ . '/../src/Minify.php'; + require_once __DIR__ . '/../src/CSS.php'; + require_once __DIR__ . '/../src/Exception.php'; +} + +error_reporting(E_ALL); +// check PHP setup for cli arguments +if (!isset($_SERVER['argv']) && !isset($argv)) { + fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL); + exit(1); +} elseif (!isset($argv)) { + $argv = $_SERVER['argv']; +} +// check if path to file given +if (!isset($argv[1])) { + fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL); + exit(1); +} +// check if script run in cli environment +if ('cli' !== php_sapi_name()) { + fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL); + exit(1); +} +// check if source file exists +if (!file_exists($argv[1])) { + fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL); + exit(1); +} + +try { + $minifier = new Minify\CSS($argv[1]); + echo $minifier->minify(); +} catch (Exception $e) { + fwrite(STDERR, $e->getMessage(), PHP_EOL); + exit(1); +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifyjs b/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifyjs new file mode 100755 index 00000000..4cbe63ff --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifyjs @@ -0,0 +1,45 @@ +#!/usr/bin/env php +<?php +use MatthiasMullie\Minify; + +// command line utility to minify JS +if (file_exists(__DIR__ . '/../../../autoload.php')) { + // if composer install + require_once __DIR__ . '/../../../autoload.php'; +} else { + require_once __DIR__ . '/../src/Minify.php'; + require_once __DIR__ . '/../src/JS.php'; + require_once __DIR__ . '/../src/Exception.php'; +} + +error_reporting(E_ALL); +// check PHP setup for cli arguments +if (!isset($_SERVER['argv']) && !isset($argv)) { + fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL); + exit(1); +} elseif (!isset($argv)) { + $argv = $_SERVER['argv']; +} +// check if path to file given +if (!isset($argv[1])) { + fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL); + exit(1); +} +// check if script run in cli environment +if ('cli' !== php_sapi_name()) { + fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL); + exit(1); +} +// check if source file exists +if (!file_exists($argv[1])) { + fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL); + exit(1); +} + +try { + $minifier = new Minify\JS($argv[1]); + echo $minifier->minify(); +} catch (Exception $e) { + fwrite(STDERR, $e->getMessage(), PHP_EOL); + exit(1); +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json b/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json new file mode 100644 index 00000000..6d81b4f9 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json @@ -0,0 +1,38 @@ +{ + "name": "matthiasmullie/minify", + "type": "library", + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "keywords": ["minify", "minifier", "css", "js", "javascript"], + "homepage": "http://www.minifier.org", + "license": "MIT", + "authors": [ + { + "name": "Matthias Mullie", + "homepage": "http://www.mullie.eu", + "email": "minify@mullie.eu", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1" + }, + "require-dev": { + "matthiasmullie/scrapbook": "~1.0", + "phpunit/phpunit": "~4.8", + "friendsofphp/php-cs-fixer": "~2.0" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ] +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_after.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_after.txt new file mode 100644 index 00000000..5c8cba7f --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_after.txt @@ -0,0 +1,7 @@ +in +public +extends +private +protected +implements +instanceof \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_before.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_before.txt new file mode 100644 index 00000000..5abf3579 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_before.txt @@ -0,0 +1,26 @@ +do +in +let +new +var +case +else +enum +void +with +class +const +yield +delete +export +import +public +static +typeof +extends +package +private +function +protected +implements +instanceof \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt new file mode 100644 index 00000000..2a3ad3c0 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt @@ -0,0 +1,63 @@ +do +if +in +for +let +new +try +var +case +else +enum +eval +null +this +true +void +with +break +catch +class +const +false +super +throw +while +yield +delete +export +import +public +return +static +switch +typeof +default +extends +finally +package +private +continue +debugger +function +arguments +interface +protected +implements +instanceof +abstract +boolean +byte +char +double +final +float +goto +int +long +native +short +synchronized +throws +transient +volatile \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators.txt new file mode 100644 index 00000000..e66229ae --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators.txt @@ -0,0 +1,46 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +~ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +! +. +[ +] +? +: +, +; +( +) +{ +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_after.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_after.txt new file mode 100644 index 00000000..71a9b709 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_after.txt @@ -0,0 +1,43 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +. +[ +] +? +: +, +; +( +) +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_before.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_before.txt new file mode 100644 index 00000000..ff50d870 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_before.txt @@ -0,0 +1,43 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +~ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +! +. +[ +? +: +, +; +( +{ diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml b/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml new file mode 100644 index 00000000..5413e24b --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml @@ -0,0 +1,31 @@ +version: '2.1' +services: + php: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./src:/var/www/src + - ./data:/var/www/data + - ./tests:/var/www/tests + - ./phpunit.xml.dist:/var/www/phpunit.xml.dist + '7.2': + extends: php + build: + args: + version: 7.2-cli + '7.1': + extends: php + build: + args: + version: 7.1-cli + '7.0': + extends: php + build: + args: + version: 7.0-cli + '5.6': + extends: php + build: + args: + version: 5.6-cli diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php new file mode 100644 index 00000000..e5a46690 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php @@ -0,0 +1,751 @@ +<?php +/** + * CSS Minifier + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ + +namespace MatthiasMullie\Minify; + +use MatthiasMullie\Minify\Exceptions\FileImportException; +use MatthiasMullie\PathConverter\ConverterInterface; +use MatthiasMullie\PathConverter\Converter; + +/** + * CSS minifier + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie <minify@mullie.eu> + * @author Tijs Verkoyen <minify@verkoyen.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +class CSS extends Minify +{ + /** + * @var int maximum inport size in kB + */ + protected $maxImportSize = 5; + + /** + * @var string[] valid import extensions + */ + protected $importExtensions = array( + 'gif' => 'data:image/gif', + 'png' => 'data:image/png', + 'jpe' => 'data:image/jpeg', + 'jpg' => 'data:image/jpeg', + 'jpeg' => 'data:image/jpeg', + 'svg' => 'data:image/svg+xml', + 'woff' => 'data:application/x-font-woff', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'xbm' => 'image/x-xbitmap', + ); + + /** + * Set the maximum size if files to be imported. + * + * Files larger than this size (in kB) will not be imported into the CSS. + * Importing files into the CSS as data-uri will save you some connections, + * but we should only import relatively small decorative images so that our + * CSS file doesn't get too bulky. + * + * @param int $size Size in kB + */ + public function setMaxImportSize($size) + { + $this->maxImportSize = $size; + } + + /** + * Set the type of extensions to be imported into the CSS (to save network + * connections). + * Keys of the array should be the file extensions & respective values + * should be the data type. + * + * @param string[] $extensions Array of file extensions + */ + public function setImportExtensions(array $extensions) + { + $this->importExtensions = $extensions; + } + + /** + * Move any import statements to the top. + * + * @param string $content Nearly finished CSS content + * + * @return string + */ + protected function moveImportsToTop($content) + { + if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) { + // remove from content + foreach ($matches[0] as $import) { + $content = str_replace($import, '', $content); + } + + // add to top + $content = implode(';', $matches[2]).';'.trim($content, ';'); + } + + return $content; + } + + /** + * Combine CSS from import statements. + * + * @import's will be loaded and their content merged into the original file, + * to save HTTP requests. + * + * @param string $source The file to combine imports for + * @param string $content The CSS content to combine imports for + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string + * + * @throws FileImportException + */ + protected function combineImports($source, $content, $parents) + { + $importRegexes = array( + // @import url(xxx) + '/ + # import statement + @import + + # whitespace + \s+ + + # open url() + url\( + + # (optional) open path enclosure + (?P<quotes>["\']?) + + # fetch path + (?P<path>.+?) + + # (optional) close path enclosure + (?P=quotes) + + # close url() + \) + + # (optional) trailing whitespace + \s* + + # (optional) media statement(s) + (?P<media>[^;]*) + + # (optional) trailing whitespace + \s* + + # (optional) closing semi-colon + ;? + + /ix', + + // @import 'xxx' + '/ + + # import statement + @import + + # whitespace + \s+ + + # open path enclosure + (?P<quotes>["\']) + + # fetch path + (?P<path>.+?) + + # close path enclosure + (?P=quotes) + + # (optional) trailing whitespace + \s* + + # (optional) media statement(s) + (?P<media>[^;]*) + + # (optional) trailing whitespace + \s* + + # (optional) closing semi-colon + ;? + + /ix', + ); + + // find all relative imports in css + $matches = array(); + foreach ($importRegexes as $importRegex) { + if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = array_merge($matches, $regexMatches); + } + } + + $search = array(); + $replace = array(); + + // loop the matches + foreach ($matches as $match) { + // get the path for the file that will be imported + $importPath = dirname($source).'/'.$match['path']; + + // only replace the import with the content if we can grab the + // content of the file + if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) { + continue; + } + + // check if current file was not imported previously in the same + // import chain. + if (in_array($importPath, $parents)) { + throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.'); + } + + // grab referenced file & minify it (which may include importing + // yet other @import statements recursively) + $minifier = new static($importPath); + $minifier->setMaxImportSize($this->maxImportSize); + $minifier->setImportExtensions($this->importExtensions); + $importContent = $minifier->execute($source, $parents); + + // check if this is only valid for certain media + if (!empty($match['media'])) { + $importContent = '@media '.$match['media'].'{'.$importContent.'}'; + } + + // add to replacement array + $search[] = $match[0]; + $replace[] = $importContent; + } + + // replace the import statements + return str_replace($search, $replace, $content); + } + + /** + * Import files into the CSS, base64-ized. + * + * @url(image.jpg) images will be loaded and their content merged into the + * original file, to save HTTP requests. + * + * @param string $source The file to import files for + * @param string $content The CSS content to import files for + * + * @return string + */ + protected function importFiles($source, $content) + { + $regex = '/url\((["\']?)(.+?)\\1\)/i'; + if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + $search = array(); + $replace = array(); + + // loop the matches + foreach ($matches as $match) { + $extension = substr(strrchr($match[2], '.'), 1); + if ($extension && !array_key_exists($extension, $this->importExtensions)) { + continue; + } + + // get the path for the file that will be imported + $path = $match[2]; + $path = dirname($source).'/'.$path; + + // only replace the import with the content if we're able to get + // the content of the file, and it's relatively small + if ($this->canImportFile($path) && $this->canImportBySize($path)) { + // grab content && base64-ize + $importContent = $this->load($path); + $importContent = base64_encode($importContent); + + // build replacement + $search[] = $match[0]; + $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')'; + } + } + + // replace the import statements + $content = str_replace($search, $replace, $content); + } + + return $content; + } + + /** + * Minify the data. + * Perform CSS optimizations. + * + * @param string[optional] $path Path to write the data to + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string The minified data + */ + public function execute($path = null, $parents = array()) + { + $content = ''; + + // loop CSS data (raw data and files) + foreach ($this->data as $source => $css) { + /* + * Let's first take out strings & comments, since we can't just + * remove whitespace anywhere. If whitespace occurs inside a string, + * we should leave it alone. E.g.: + * p { content: "a test" } + */ + $this->extractStrings(); + $this->stripComments(); + $this->extractCalcs(); + $css = $this->replace($css); + + $css = $this->stripWhitespace($css); + $css = $this->shortenColors($css); + $css = $this->shortenZeroes($css); + $css = $this->shortenFontWeights($css); + $css = $this->stripEmptyTags($css); + + // restore the string we've extracted earlier + $css = $this->restoreExtractedData($css); + + $source = is_int($source) ? '' : $source; + $parents = $source ? array_merge($parents, array($source)) : $parents; + $css = $this->combineImports($source, $css, $parents); + $css = $this->importFiles($source, $css); + + /* + * If we'll save to a new path, we'll have to fix the relative paths + * to be relative no longer to the source file, but to the new path. + * If we don't write to a file, fall back to same path so no + * conversion happens (because we still want it to go through most + * of the move code, which also addresses url() & @import syntax...) + */ + $converter = $this->getPathConverter($source, $path ?: $source); + $css = $this->move($converter, $css); + + // combine css + $content .= $css; + } + + $content = $this->moveImportsToTop($content); + + return $content; + } + + /** + * Moving a css file should update all relative urls. + * Relative references (e.g. ../images/image.gif) in a certain css file, + * will have to be updated when a file is being saved at another location + * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). + * + * @param ConverterInterface $converter Relative path converter + * @param string $content The CSS content to update relative urls for + * + * @return string + */ + protected function move(ConverterInterface $converter, $content) + { + /* + * Relative path references will usually be enclosed by url(). @import + * is an exception, where url() is not necessary around the path (but is + * allowed). + * This *could* be 1 regular expression, where both regular expressions + * in this array are on different sides of a |. But we're using named + * patterns in both regexes, the same name on both regexes. This is only + * possible with a (?J) modifier, but that only works after a fairly + * recent PCRE version. That's why I'm doing 2 separate regular + * expressions & combining the matches after executing of both. + */ + $relativeRegexes = array( + // url(xxx) + '/ + # open url() + url\( + + \s* + + # open path enclosure + (?P<quotes>["\'])? + + # fetch path + (?P<path>.+?) + + # close path enclosure + (?(quotes)(?P=quotes)) + + \s* + + # close url() + \) + + /ix', + + // @import "xxx" + '/ + # import statement + @import + + # whitespace + \s+ + + # we don\'t have to check for @import url(), because the + # condition above will already catch these + + # open path enclosure + (?P<quotes>["\']) + + # fetch path + (?P<path>.+?) + + # close path enclosure + (?P=quotes) + + /ix', + ); + + // find all relative urls in css + $matches = array(); + foreach ($relativeRegexes as $relativeRegex) { + if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = array_merge($matches, $regexMatches); + } + } + + $search = array(); + $replace = array(); + + // loop all urls + foreach ($matches as $match) { + // determine if it's a url() or an @import match + $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url'); + + $url = $match['path']; + if ($this->canImportByPath($url)) { + // attempting to interpret GET-params makes no sense, so let's discard them for awhile + $params = strrchr($url, '?'); + $url = $params ? substr($url, 0, -strlen($params)) : $url; + + // fix relative url + $url = $converter->convert($url); + + // now that the path has been converted, re-apply GET-params + $url .= $params; + } + + /* + * Urls with control characters above 0x7e should be quoted. + * According to Mozilla's parser, whitespace is only allowed at the + * end of unquoted urls. + * Urls with `)` (as could happen with data: uris) should also be + * quoted to avoid being confused for the url() closing parentheses. + * And urls with a # have also been reported to cause issues. + * Urls with quotes inside should also remain escaped. + * + * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation + * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 + * @see https://github.com/matthiasmullie/minify/issues/193 + */ + $url = trim($url); + if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) { + $url = $match['quotes'] . $url . $match['quotes']; + } + + // build replacement + $search[] = $match[0]; + if ($type === 'url') { + $replace[] = 'url('.$url.')'; + } elseif ($type === 'import') { + $replace[] = '@import "'.$url.'"'; + } + } + + // replace urls + return str_replace($search, $replace, $content); + } + + /** + * Shorthand hex color codes. + * #FF0000 -> #F00. + * + * @param string $content The CSS content to shorten the hex color codes for + * + * @return string + */ + protected function shortenColors($content) + { + $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content); + + // remove alpha channel if it's pointless... + $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content); + $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content); + + $colors = array( + // we can shorten some even more by replacing them with their color name + '#F0FFFF' => 'azure', + '#F5F5DC' => 'beige', + '#A52A2A' => 'brown', + '#FF7F50' => 'coral', + '#FFD700' => 'gold', + '#808080' => 'gray', + '#008000' => 'green', + '#4B0082' => 'indigo', + '#FFFFF0' => 'ivory', + '#F0E68C' => 'khaki', + '#FAF0E6' => 'linen', + '#800000' => 'maroon', + '#000080' => 'navy', + '#808000' => 'olive', + '#CD853F' => 'peru', + '#FFC0CB' => 'pink', + '#DDA0DD' => 'plum', + '#800080' => 'purple', + '#F00' => 'red', + '#FA8072' => 'salmon', + '#A0522D' => 'sienna', + '#C0C0C0' => 'silver', + '#FFFAFA' => 'snow', + '#D2B48C' => 'tan', + '#FF6347' => 'tomato', + '#EE82EE' => 'violet', + '#F5DEB3' => 'wheat', + // or the other way around + 'WHITE' => '#fff', + 'BLACK' => '#000', + ); + + return preg_replace_callback( + '/(?<=[: ])('.implode(array_keys($colors), '|').')(?=[; }])/i', + function ($match) use ($colors) { + return $colors[strtoupper($match[0])]; + }, + $content + ); + } + + /** + * Shorten CSS font weights. + * + * @param string $content The CSS content to shorten the font weights for + * + * @return string + */ + protected function shortenFontWeights($content) + { + $weights = array( + 'normal' => 400, + 'bold' => 700, + ); + + $callback = function ($match) use ($weights) { + return $match[1].$weights[$match[2]]; + }; + + return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content); + } + + /** + * Shorthand 0 values to plain 0, instead of e.g. -0em. + * + * @param string $content The CSS content to shorten the zero values for + * + * @return string + */ + protected function shortenZeroes($content) + { + // we don't want to strip units in `calc()` expressions: + // `5px - 0px` is valid, but `5px - 0` is not + // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but + // `10 * 0` is invalid + // we've extracted calcs earlier, so we don't need to worry about this + + // reusable bits of code throughout these regexes: + // before & after are used to make sure we don't match lose unintended + // 0-like values (e.g. in #000, or in http://url/1.0) + // units can be stripped from 0 values, or used to recognize non 0 + // values (where wa may be able to strip a .0 suffix) + $before = '(?<=[:(, ])'; + $after = '(?=[ ,);}])'; + $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)'; + + // strip units after zeroes (0px -> 0) + // NOTE: it should be safe to remove all units for a 0 value, but in + // practice, Webkit (especially Safari) seems to stumble over at least + // 0%, potentially other units as well. Only stripping 'px' for now. + // @see https://github.com/matthiasmullie/minify/issues/60 + $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content); + + // strip 0-digits (.0 -> 0) + $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content); + // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px + $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content); + // strip trailing 0: 50.00 -> 50, 50.00px -> 50px + $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content); + // strip leading 0: 0.1 -> .1, 01.1 -> 1.1 + $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content); + + // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) + $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content); + + // IE doesn't seem to understand a unitless flex-basis value (correct - + // it goes against the spec), so let's add it in again (make it `%`, + // which is only 1 char: 0%, 0px, 0 anything, it's all just the same) + // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex + $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content); + $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content); + + return $content; + } + + /** + * Strip empty tags from source code. + * + * @param string $content + * + * @return string + */ + protected function stripEmptyTags($content) + { + $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content); + $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '/*'.$count.'*/'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); + + $this->registerPattern('/\/\*.*?\*\//s', ''); + } + + /** + * Strip whitespace. + * + * @param string $content The CSS content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // remove leading & trailing whitespace + $content = preg_replace('/^\s*/m', '', $content); + $content = preg_replace('/\s*$/m', '', $content); + + // replace newlines with a single space + $content = preg_replace('/\s+/', ' ', $content); + + // remove whitespace around meta characters + // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex + $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); + $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content); + $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content); + $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content); + + // whitespace around + and - can only be stripped inside some pseudo- + // classes, like `:nth-child(3+2n)` + // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or + // selectors like `div.weird- p` + $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type'); + $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); + + // remove semicolon/whitespace followed by closing bracket + $content = str_replace(';}', '}', $content); + + return trim($content); + } + + /** + * Replace all `calc()` occurrences. + */ + protected function extractCalcs() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $length = strlen($match[1]); + $expr = ''; + $opened = 0; + + for ($i = 0; $i < $length; $i++) { + $char = $match[1][$i]; + $expr .= $char; + if ($char === '(') { + $opened++; + } elseif ($char === ')' && --$opened === 0) { + break; + } + } + $rest = str_replace($expr, '', $match[1]); + $expr = trim(substr($expr, 1, -1)); + + $count = count($minifier->extracted); + $placeholder = 'calc('.$count.')'; + $minifier->extracted[$placeholder] = 'calc('.$expr.')'; + + return $placeholder.$rest; + }; + + $this->registerPattern('/calc(\(.+?)(?=$|;|calc\()/', $callback); + } + + /** + * Check if file is small enough to be imported. + * + * @param string $path The path to the file + * + * @return bool + */ + protected function canImportBySize($path) + { + return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024; + } + + /** + * Check if file a file can be imported, going by the path. + * + * @param string $path + * + * @return bool + */ + protected function canImportByPath($path) + { + return preg_match('/^(data:|https?:|\\/)/', $path) === 0; + } + + /** + * Return a converter to update relative paths to be relative to the new + * destination. + * + * @param string $source + * @param string $target + * + * @return ConverterInterface + */ + protected function getPathConverter($source, $target) + { + return new Converter($source, $target); + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php new file mode 100644 index 00000000..d03898f0 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php @@ -0,0 +1,20 @@ +<?php +/** + * Base Exception + * + * @deprecated Use Exceptions\BasicException instead + * + * @author Matthias Mullie <minify@mullie.eu> + */ +namespace MatthiasMullie\Minify; + +/** + * Base Exception Class + * @deprecated Use Exceptions\BasicException instead + * + * @package Minify + * @author Matthias Mullie <minify@mullie.eu> + */ +abstract class Exception extends \Exception +{ +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php new file mode 100644 index 00000000..af5e81bc --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php @@ -0,0 +1,23 @@ +<?php +/** + * Basic exception + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +use MatthiasMullie\Minify\Exception; + +/** + * Basic Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie <minify@mullie.eu> + */ +abstract class BasicException extends Exception +{ +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php new file mode 100644 index 00000000..912a2c90 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php @@ -0,0 +1,21 @@ +<?php +/** + * File Import Exception + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +/** + * File Import Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie <minify@mullie.eu> + */ +class FileImportException extends BasicException +{ +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php new file mode 100644 index 00000000..b172eb48 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php @@ -0,0 +1,21 @@ +<?php +/** + * IO Exception + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +/** + * IO Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie <minify@mullie.eu> + */ +class IOException extends BasicException +{ +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php new file mode 100644 index 00000000..92389cdd --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php @@ -0,0 +1,612 @@ +<?php +/** + * JavaScript minifier + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify; + +/** + * JavaScript Minifier Class + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie <minify@mullie.eu> + * @author Tijs Verkoyen <minify@verkoyen.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +class JS extends Minify +{ + /** + * Var-matching regex based on http://stackoverflow.com/a/9337047/802993. + * + * Note that regular expressions using that bit must have the PCRE_UTF8 + * pattern modifier (/u) set. + * + * @var string + */ + const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b'; + + /** + * Full list of JavaScript reserved words. + * Will be loaded from /data/js/keywords_reserved.txt. + * + * @see https://mathiasbynens.be/notes/reserved-keywords + * + * @var string[] + */ + protected $keywordsReserved = array(); + + /** + * List of JavaScript reserved words that accept a <variable, value, ...> + * after them. Some end of lines are not the end of a statement, like with + * these keywords. + * + * E.g.: we shouldn't insert a ; after this else + * else + * console.log('this is quite fine') + * + * Will be loaded from /data/js/keywords_before.txt + * + * @var string[] + */ + protected $keywordsBefore = array(); + + /** + * List of JavaScript reserved words that accept a <variable, value, ...> + * before them. Some end of lines are not the end of a statement, like when + * continued by one of these keywords on the newline. + * + * E.g.: we shouldn't insert a ; before this instanceof + * variable + * instanceof String + * + * Will be loaded from /data/js/keywords_after.txt + * + * @var string[] + */ + protected $keywordsAfter = array(); + + /** + * List of all JavaScript operators. + * + * Will be loaded from /data/js/operators.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operators = array(); + + /** + * List of JavaScript operators that accept a <variable, value, ...> after + * them. Some end of lines are not the end of a statement, like with these + * operators. + * + * Note: Most operators are fine, we've only removed ++ and --. + * ++ & -- have to be joined with the value they're in-/decrementing. + * + * Will be loaded from /data/js/operators_before.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsBefore = array(); + + /** + * List of JavaScript operators that accept a <variable, value, ...> before + * them. Some end of lines are not the end of a statement, like when + * continued by one of these operators on the newline. + * + * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~. + * There can't be a newline separating ! or ~ and whatever it is negating. + * ++ & -- have to be joined with the value they're in-/decrementing. + * ) & ] are "special" in that they have lots or usecases. () for example + * is used for function calls, for grouping, in if () and for (), ... + * + * Will be loaded from /data/js/operators_after.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsAfter = array(); + + /** + * {@inheritdoc} + */ + public function __construct() + { + call_user_func_array(array('parent', '__construct'), func_get_args()); + + $dataDir = __DIR__.'/../data/js/'; + $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES; + $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options); + $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options); + $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options); + $this->operators = file($dataDir.'operators.txt', $options); + $this->operatorsBefore = file($dataDir.'operators_before.txt', $options); + $this->operatorsAfter = file($dataDir.'operators_after.txt', $options); + } + + /** + * Minify the data. + * Perform JS optimizations. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function execute($path = null) + { + $content = ''; + + /* + * Let's first take out strings, comments and regular expressions. + * All of these can contain JS code-like characters, and we should make + * sure any further magic ignores anything inside of these. + * + * Consider this example, where we should not strip any whitespace: + * var str = "a test"; + * + * Comments will be removed altogether, strings and regular expressions + * will be replaced by placeholder text, which we'll restore later. + */ + $this->extractStrings('\'"`'); + $this->stripComments(); + $this->extractRegex(); + + // loop files + foreach ($this->data as $source => $js) { + // take out strings, comments & regex (for which we've registered + // the regexes just a few lines earlier) + $js = $this->replace($js); + + $js = $this->propertyNotation($js); + $js = $this->shortenBools($js); + $js = $this->stripWhitespace($js); + + // combine js: separating the scripts by a ; + $content .= $js.";"; + } + + // clean up leftover `;`s from the combination of multiple scripts + $content = ltrim($content, ';'); + $content = (string) substr($content, 0, -1); + + /* + * Earlier, we extracted strings & regular expressions and replaced them + * with placeholder text. This will restore them. + */ + $content = $this->restoreExtractedData($content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '/*'.$count.'*/'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + // multi-line comments + $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); + $this->registerPattern('/\/\*.*?\*\//s', ''); + + // single-line comments + $this->registerPattern('/\/\/.*$/m', ''); + } + + /** + * JS can have /-delimited regular expressions, like: /ab+c/.match(string). + * + * The content inside the regex can contain characters that may be confused + * for JS code: e.g. it could contain whitespace it needs to match & we + * don't want to strip whitespace in there. + * + * The regex can be pretty simple: we don't have to care about comments, + * (which also use slashes) because stripComments() will have stripped those + * already. + * + * This method will replace all string content with simple REGEX# + * placeholder text, so we've rid all regular expressions from characters + * that may be misinterpreted. Original regex content will be saved in + * $this->extracted and after doing all other minifying, we can restore the + * original content via restoreRegex() + */ + protected function extractRegex() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '"'.$count.'"'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + + // match all chars except `/` and `\` + // `\` is allowed though, along with whatever char follows (which is the + // one being escaped) + // this should allow all chars, except for an unescaped `/` (= the one + // closing the regex) + // then also ignore bare `/` inside `[]`, where they don't need to be + // escaped: anything inside `[]` can be ignored safely + $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*'; + + // a regular expression can only be followed by a few operators or some + // of the RegExp methods (a `\` followed by a variable or value is + // likely part of a division, not a regex) + $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof'); + $before = '([=:,;\+\-\*\/\}\(\{\[&\|!]|^|'.implode('|', $keywords).')\s*'; + $propertiesAndMethods = array( + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 + 'constructor', + 'flags', + 'global', + 'ignoreCase', + 'multiline', + 'source', + 'sticky', + 'unicode', + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2 + 'compile(', + 'exec(', + 'test(', + 'toSource(', + 'toString(', + ); + $delimiters = array_fill(0, count($propertiesAndMethods), '/'); + $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters); + $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))'; + $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); + + // regular expressions following a `)` are rather annoying to detect... + // quite often, `/` after `)` is a division operator & if it happens to + // be followed by another one (or a comment), it is likely to be + // confused for a regular expression + // however, it's perfectly possible for a regex to follow a `)`: after + // a single-line `if()`, `while()`, ... statement, for example + // since, when they occur like that, they're always the start of a + // statement, there's only a limited amount of ways they can be useful: + // by calling the regex methods directly + // if a regex following `)` is not followed by `.<property or method>`, + // it's quite likely not a regex + $before = '\)\s*'; + $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))'; + $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); + + // 1 more edge case: a regex can be followed by a lot more operators or + // keywords if there's a newline (ASI) in between, where the operator + // actually starts a new statement + // (https://github.com/matthiasmullie/minify/issues/56) + $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); + $after = '(?=\s*\n\s*('.implode('|', $operators).'))'; + $this->registerPattern('/'.$pattern.$after.'/', $callback); + } + + /** + * Strip whitespace. + * + * We won't strip *all* whitespace, but as much as possible. The thing that + * we'll preserve are newlines we're unsure about. + * JavaScript doesn't require statements to be terminated with a semicolon. + * It will automatically fix missing semicolons with ASI (automatic semi- + * colon insertion) at the end of line causing errors (without semicolon.) + * + * Because it's sometimes hard to tell if a newline is part of a statement + * that should be terminated or not, we'll just leave some of them alone. + * + * @param string $content The content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // uniform line endings, make them all line feed + $content = str_replace(array("\r\n", "\r"), "\n", $content); + + // collapse all non-line feed whitespace into a single space + $content = preg_replace('/[^\S\n]+/', ' ', $content); + + // strip leading & trailing whitespace + $content = str_replace(array(" \n", "\n "), "\n", $content); + + // collapse consecutive line feeds into just 1 + $content = preg_replace('/\n+/', "\n", $content); + + $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/'); + $operators = $this->getOperatorsForRegex($this->operators, '/'); + $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/'); + $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/'); + + // strip whitespace that ends in (or next line begin with) an operator + // that allows statements to be broken up over multiple lines + unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); + $content = preg_replace( + array( + '/('.implode('|', $operatorsBefore).')\s+/', + '/\s+('.implode('|', $operatorsAfter).')/', + ), + '\\1', + $content + ); + + // make sure + and - can't be mistaken for, or joined into ++ and -- + $content = preg_replace( + array( + '/(?<![\+\-])\s*([\+\-])(?![\+\-])/', + '/(?<![\+\-])([\+\-])\s*(?![\+\-])/', + ), + '\\1', + $content + ); + + // collapse whitespace around reserved words into single space + $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content); + $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content); + + /* + * We didn't strip whitespace after a couple of operators because they + * could be used in different contexts and we can't be sure it's ok to + * strip the newlines. However, we can safely strip any non-line feed + * whitespace that follows them. + */ + $operatorsDiffBefore = array_diff($operators, $operatorsBefore); + $operatorsDiffAfter = array_diff($operators, $operatorsAfter); + $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content); + $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content); + + /* + * Whitespace after `return` can be omitted in a few occasions + * (such as when followed by a string or regex) + * Same for whitespace in between `)` and `{`, or between `{` and some + * keywords. + */ + $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content); + $content = preg_replace('/\)\s+\{/', '){', $content); + $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content); + + /* + * Get rid of double semicolons, except where they can be used like: + * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))". + * I'll safeguard these double semicolons inside for-loops by + * temporarily replacing them with an invalid condition: they won't have + * a double semicolon and will be easy to spot to restore afterwards. + */ + $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content); + $content = preg_replace('/;+/', ';', $content); + $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content); + + /* + * Next, we'll be removing all semicolons where ASI kicks in. + * for-loops however, can have an empty body (ending in only a + * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);` + * Here, nothing happens during the loop; it's just used to keep + * increasing `i`. With that ; omitted, the next line would be expected + * to be the for-loop's body... Same goes for while loops. + * I'm going to double that semicolon (if any) so after the next line, + * which strips semicolons here & there, we're still left with this one. + */ + $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content); + $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content); + /* + * Below will also keep `;` after a `do{}while();` along with `while();` + * While these could be stripped after do-while, detecting this + * distinction is cumbersome, so I'll play it safe and make sure `;` + * after any kind of `while` is kept. + */ + $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content); + + /* + * We also can't strip empty else-statements. Even though they're + * useless and probably shouldn't be in the code in the first place, we + * shouldn't be stripping the `;` that follows it as it breaks the code. + * We can just remove those useless else-statements completely. + * + * @see https://github.com/matthiasmullie/minify/issues/91 + */ + $content = preg_replace('/else;/s', '', $content); + + /* + * We also don't really want to terminate statements followed by closing + * curly braces (which we've ignored completely up until now) or end-of- + * script: ASI will kick in here & we're all about minifying. + * Semicolons at beginning of the file don't make any sense either. + */ + $content = preg_replace('/;(\}|$)/s', '\\1', $content); + $content = ltrim($content, ';'); + + // get rid of remaining whitespace af beginning/end + return trim($content); + } + + /** + * We'll strip whitespace around certain operators with regular expressions. + * This will prepare the given array by escaping all characters. + * + * @param string[] $operators + * @param string $delimiter + * + * @return string[] + */ + protected function getOperatorsForRegex(array $operators, $delimiter = '/') + { + // escape operators for use in regex + $delimiters = array_fill(0, count($operators), $delimiter); + $escaped = array_map('preg_quote', $operators, $delimiters); + + $operators = array_combine($operators, $escaped); + + // ignore + & - for now, they'll get special treatment + unset($operators['+'], $operators['-']); + + // dot can not just immediately follow a number; it can be confused for + // decimal point, or calling a method on it, e.g. 42 .toString() + $operators['.'] = '(?<![0-9]\s)\.'; + + // don't confuse = with other assignment shortcuts (e.g. +=) + $chars = preg_quote('+-*\=<>%&|', $delimiter); + $operators['='] = '(?<!['.$chars.'])\='; + + return $operators; + } + + /** + * We'll strip whitespace around certain keywords with regular expressions. + * This will prepare the given array by escaping all characters. + * + * @param string[] $keywords + * @param string $delimiter + * + * @return string[] + */ + protected function getKeywordsForRegex(array $keywords, $delimiter = '/') + { + // escape keywords for use in regex + $delimiter = array_fill(0, count($keywords), $delimiter); + $escaped = array_map('preg_quote', $keywords, $delimiter); + + // add word boundaries + array_walk($keywords, function ($value) { + return '\b'.$value.'\b'; + }); + + $keywords = array_combine($keywords, $escaped); + + return $keywords; + } + + /** + * Replaces all occurrences of array['key'] by array.key. + * + * @param string $content + * + * @return string + */ + protected function propertyNotation($content) + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $keywords = $this->keywordsReserved; + $callback = function ($match) use ($minifier, $keywords) { + $property = trim($minifier->extracted[$match[1]], '\'"'); + + /* + * Check if the property is a reserved keyword. In this context (as + * property of an object literal/array) it shouldn't matter, but IE8 + * freaks out with "Expected identifier". + */ + if (in_array($property, $keywords)) { + return $match[0]; + } + + /* + * See if the property is in a variable-like format (e.g. + * array['key-here'] can't be replaced by array.key-here since '-' + * is not a valid character there. + */ + if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) { + return $match[0]; + } + + return '.'.$property; + }; + + /* + * Figure out if previous character is a variable name (of the array + * we want to use property notation on) - this is to make sure + * standalone ['value'] arrays aren't confused for keys-of-an-array. + * We can (and only have to) check the last character, because PHP's + * regex implementation doesn't allow unfixed-length look-behind + * assertions. + */ + preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar); + $previousChar = $previousChar[1]; + + /* + * Make sure word preceding the ['value'] is not a keyword, e.g. + * return['x']. Because -again- PHP's regex implementation doesn't allow + * unfixed-length look-behind assertions, I'm just going to do a lot of + * separate look-behind assertions, one for each keyword. + */ + $keywords = $this->getKeywordsForRegex($keywords); + $keywords = '(?<!'.implode(')(?<!', $keywords).')'; + + return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content); + } + + /** + * Replaces true & false by !0 and !1. + * + * @param string $content + * + * @return string + */ + protected function shortenBools($content) + { + /* + * 'true' or 'false' could be used as property names (which may be + * followed by whitespace) - we must not replace those! + * Since PHP doesn't allow variable-length (to account for the + * whitespace) lookbehind assertions, I need to capture the leading + * character and check if it's a `.` + */ + $callback = function ($match) { + if (trim($match[1]) === '.') { + return $match[0]; + } + + return $match[1].($match[2] === 'true' ? '!0' : '!1'); + }; + $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content); + + // for(;;) is exactly the same as while(true), but shorter :) + $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content); + + // now make sure we didn't turn any do ... while(true) into do ... for(;;) + preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + // go backward to make sure positional offsets aren't altered when $content changes + $dos = array_reverse($dos); + foreach ($dos as $do) { + $offsetDo = $do[0][1]; + + // find all `while` (now `for`) following `do`: one of those must be + // associated with the `do` and be turned back into `while` + preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo); + foreach ($whiles as $while) { + $offsetWhile = $while[0][1]; + + $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo); + $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo); + if ($open === $close) { + // only restore `while` if amount of `{` and `}` are the same; + // otherwise, that `for` isn't associated with this `do` + $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)')); + break; + } + } + } + + return $content; + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Minify.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Minify.php new file mode 100644 index 00000000..e5fefe6f --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Minify.php @@ -0,0 +1,459 @@ +<?php +/** + * Abstract minifier class + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify; + +use MatthiasMullie\Minify\Exceptions\IOException; +use Psr\Cache\CacheItemInterface; + +/** + * Abstract minifier class. + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +abstract class Minify +{ + /** + * The data to be minified. + * + * @var string[] + */ + protected $data = array(); + + /** + * Array of patterns to match. + * + * @var string[] + */ + protected $patterns = array(); + + /** + * This array will hold content of strings and regular expressions that have + * been extracted from the JS source code, so we can reliably match "code", + * without having to worry about potential "code-like" characters inside. + * + * @var string[] + */ + public $extracted = array(); + + /** + * Init the minify class - optionally, code may be passed along already. + */ + public function __construct(/* $data = null, ... */) + { + // it's possible to add the source through the constructor as well ;) + if (func_num_args()) { + call_user_func_array(array($this, 'add'), func_get_args()); + } + } + + /** + * Add a file or straight-up code to be minified. + * + * @param string|string[] $data + * + * @return static + */ + public function add($data /* $data = null, ... */) + { + // bogus "usage" of parameter $data: scrutinizer warns this variable is + // not used (we're using func_get_args instead to support overloading), + // but it still needs to be defined because it makes no sense to have + // this function without argument :) + $args = array($data) + func_get_args(); + + // this method can be overloaded + foreach ($args as $data) { + if (is_array($data)) { + call_user_func_array(array($this, 'add'), $data); + continue; + } + + // redefine var + $data = (string) $data; + + // load data + $value = $this->load($data); + $key = ($data != $value) ? $data : count($this->data); + + // replace CR linefeeds etc. + // @see https://github.com/matthiasmullie/minify/pull/139 + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // store data + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Minify the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function minify($path = null) + { + $content = $this->execute($path); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify & gzip the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * @param int[optional] $level Compression level, from 0 to 9 + * + * @return string The minified & gzipped data + */ + public function gzip($path = null, $level = 9) + { + $content = $this->execute($path); + $content = gzencode($content, $level, FORCE_GZIP); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify the data & write it to a CacheItemInterface object. + * + * @param CacheItemInterface $item Cache item to write the data to + * + * @return CacheItemInterface Cache item with the minifier data + */ + public function cache(CacheItemInterface $item) + { + $content = $this->execute(); + $item->set($content); + + return $item; + } + + /** + * Minify the data. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + abstract public function execute($path = null); + + /** + * Load data. + * + * @param string $data Either a path to a file or the content itself + * + * @return string + */ + protected function load($data) + { + // check if the data is a file + if ($this->canImportFile($data)) { + $data = file_get_contents($data); + + // strip BOM, if any + if (substr($data, 0, 3) == "\xef\xbb\xbf") { + $data = substr($data, 3); + } + } + + return $data; + } + + /** + * Save to file. + * + * @param string $content The minified data + * @param string $path The path to save the minified data to + * + * @throws IOException + */ + protected function save($content, $path) + { + $handler = $this->openFileForWriting($path); + + $this->writeToFile($handler, $content); + + @fclose($handler); + } + + /** + * Register a pattern to execute against the source content. + * + * @param string $pattern PCRE pattern + * @param string|callable $replacement Replacement value for matched pattern + */ + protected function registerPattern($pattern, $replacement = '') + { + // study the pattern, we'll execute it more than once + $pattern .= 'S'; + + $this->patterns[] = array($pattern, $replacement); + } + + /** + * We can't "just" run some regular expressions against JavaScript: it's a + * complex language. E.g. having an occurrence of // xyz would be a comment, + * unless it's used within a string. Of you could have something that looks + * like a 'string', but inside a comment. + * The only way to accurately replace these pieces is to traverse the JS one + * character at a time and try to find whatever starts first. + * + * @param string $content The content to replace patterns in + * + * @return string The (manipulated) content + */ + protected function replace($content) + { + $processed = ''; + $positions = array_fill(0, count($this->patterns), -1); + $matches = array(); + + while ($content) { + // find first match for all patterns + foreach ($this->patterns as $i => $pattern) { + list($pattern, $replacement) = $pattern; + + // we can safely ignore patterns for positions we've unset earlier, + // because we know these won't show up anymore + if (array_key_exists($i, $positions) == false) { + continue; + } + + // no need to re-run matches that are still in the part of the + // content that hasn't been processed + if ($positions[$i] >= 0) { + continue; + } + + $match = null; + if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) { + $matches[$i] = $match; + + // we'll store the match position as well; that way, we + // don't have to redo all preg_matches after changing only + // the first (we'll still know where those others are) + $positions[$i] = $match[0][1]; + } else { + // if the pattern couldn't be matched, there's no point in + // executing it again in later runs on this same content; + // ignore this one until we reach end of content + unset($matches[$i], $positions[$i]); + } + } + + // no more matches to find: everything's been processed, break out + if (!$matches) { + $processed .= $content; + break; + } + + // see which of the patterns actually found the first thing (we'll + // only want to execute that one, since we're unsure if what the + // other found was not inside what the first found) + $discardLength = min($positions); + $firstPattern = array_search($discardLength, $positions); + $match = $matches[$firstPattern][0][0]; + + // execute the pattern that matches earliest in the content string + list($pattern, $replacement) = $this->patterns[$firstPattern]; + $replacement = $this->replacePattern($pattern, $replacement, $content); + + // figure out which part of the string was unmatched; that's the + // part we'll execute the patterns on again next + $content = (string) substr($content, $discardLength); + $unmatched = (string) substr($content, strpos($content, $match) + strlen($match)); + + // move the replaced part to $processed and prepare $content to + // again match batch of patterns against + $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched)); + $content = $unmatched; + + // first match has been replaced & that content is to be left alone, + // the next matches will start after this replacement, so we should + // fix their offsets + foreach ($positions as $i => $position) { + $positions[$i] -= $discardLength + strlen($match); + } + } + + return $processed; + } + + /** + * This is where a pattern is matched against $content and the matches + * are replaced by their respective value. + * This function will be called plenty of times, where $content will always + * move up 1 character. + * + * @param string $pattern Pattern to match + * @param string|callable $replacement Replacement value + * @param string $content Content to match pattern against + * + * @return string + */ + protected function replacePattern($pattern, $replacement, $content) + { + if (is_callable($replacement)) { + return preg_replace_callback($pattern, $replacement, $content, 1, $count); + } else { + return preg_replace($pattern, $replacement, $content, 1, $count); + } + } + + /** + * Strings are a pattern we need to match, in order to ignore potential + * code-like content inside them, but we just want all of the string + * content to remain untouched. + * + * This method will replace all string content with simple STRING# + * placeholder text, so we've rid all strings from characters that may be + * misinterpreted. Original string content will be saved in $this->extracted + * and after doing all other minifying, we can restore the original content + * via restoreStrings(). + * + * @param string[optional] $chars + * @param string[optional] $placeholderPrefix + */ + protected function extractStrings($chars = '\'"', $placeholderPrefix = '') + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier, $placeholderPrefix) { + // check the second index here, because the first always contains a quote + if ($match[2] === '') { + /* + * Empty strings need no placeholder; they can't be confused for + * anything else anyway. + * But we still needed to match them, for the extraction routine + * to skip over this particular string. + */ + return $match[0]; + } + + $count = count($minifier->extracted); + $placeholder = $match[1].$placeholderPrefix.$count.$match[1]; + $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1]; + + return $placeholder; + }; + + /* + * The \\ messiness explained: + * * Don't count ' or " as end-of-string if it's escaped (has backslash + * in front of it) + * * Unless... that backslash itself is escaped (another leading slash), + * in which case it's no longer escaping the ' or " + * * So there can be either no backslash, or an even number + * * multiply all of that times 4, to account for the escaping that has + * to be done to pass the backslash into the PHP string without it being + * considered as escape-char (times 2) and to get it in the regex, + * escaped (times 2) + */ + $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback); + } + + /** + * This method will restore all extracted data (strings, regexes) that were + * replaced with placeholder text in extract*(). The original content was + * saved in $this->extracted. + * + * @param string $content + * + * @return string + */ + protected function restoreExtractedData($content) + { + if (!$this->extracted) { + // nothing was extracted, nothing to restore + return $content; + } + + $content = strtr($content, $this->extracted); + + $this->extracted = array(); + + return $content; + } + + /** + * Check if the path is a regular file and can be read. + * + * @param string $path + * + * @return bool + */ + protected function canImportFile($path) + { + $parsed = parse_url($path); + if ( + // file is elsewhere + isset($parsed['host']) || + // file responds to queries (may change, or need to bypass cache) + isset($parsed['query']) + ) { + return false; + } + + return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path); + } + + /** + * Attempts to open file specified by $path for writing. + * + * @param string $path The path to the file + * + * @return resource Specifier for the target file + * + * @throws IOException + */ + protected function openFileForWriting($path) + { + if (($handler = @fopen($path, 'w')) === false) { + throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.'); + } + + return $handler; + } + + /** + * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. + * + * @param resource $handler The resource to write to + * @param string $content The content to write + * @param string $path The path to the file (for exception printing only) + * + * @throws IOException + */ + protected function writeToFile($handler, $content, $path = '') + { + if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) { + throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.'); + } + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/LICENSE b/data/web/inc/lib/vendor/matthiasmullie/path-converter/LICENSE new file mode 100644 index 00000000..491295ad --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2015 Matthias Mullie + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/composer.json b/data/web/inc/lib/vendor/matthiasmullie/path-converter/composer.json new file mode 100644 index 00000000..1cb6a4c5 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/composer.json @@ -0,0 +1,28 @@ +{ + "name": "matthiasmullie/path-converter", + "type": "library", + "description": "Relative path converter", + "keywords": ["relative", "path", "converter", "paths"], + "homepage": "http://github.com/matthiasmullie/path-converter", + "license": "MIT", + "authors": [ + { + "name": "Matthias Mullie", + "homepage": "http://www.mullie.eu", + "email": "pathconverter@mullie.eu", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-pcre": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/Converter.php b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/Converter.php new file mode 100644 index 00000000..519d3c84 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/Converter.php @@ -0,0 +1,196 @@ +<?php + +namespace MatthiasMullie\PathConverter; + +/** + * Convert paths relative from 1 file to another. + * + * E.g. + * ../../images/icon.jpg relative to /css/imports/icons.css + * becomes + * ../images/icon.jpg relative to /css/minified.css + * + * Please report bugs on https://github.com/matthiasmullie/path-converter/issues + * + * @author Matthias Mullie <pathconverter@mullie.eu> + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +class Converter implements ConverterInterface +{ + /** + * @var string + */ + protected $from; + + /** + * @var string + */ + protected $to; + + /** + * @param string $from The original base path (directory, not file!) + * @param string $to The new base path (directory, not file!) + * @param string $root Root directory (defaults to `getcwd`) + */ + public function __construct($from, $to, $root = '') + { + $shared = $this->shared($from, $to); + if ($shared === '') { + // when both paths have nothing in common, one of them is probably + // absolute while the other is relative + $root = $root ?: getcwd(); + $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from); + $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to); + + // or traveling the tree via `..` + // attempt to resolve path, or assume it's fine if it doesn't exist + $from = @realpath($from) ?: $from; + $to = @realpath($to) ?: $to; + } + + $from = $this->dirname($from); + $to = $this->dirname($to); + + $from = $this->normalize($from); + $to = $this->normalize($to); + + $this->from = $from; + $this->to = $to; + } + + /** + * Normalize path. + * + * @param string $path + * + * @return string + */ + protected function normalize($path) + { + // deal with different operating systems' directory structure + $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/'); + + /* + * Example: + * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif + * to + * /home/forkcms/frontend/core/layout/images/img.gif + */ + do { + $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count); + } while ($count); + + return $path; + } + + /** + * Figure out the shared path of 2 locations. + * + * Example: + * /home/forkcms/frontend/core/layout/images/img.gif + * and + * /home/forkcms/frontend/cache/minified_css + * share + * /home/forkcms/frontend + * + * @param string $path1 + * @param string $path2 + * + * @return string + */ + protected function shared($path1, $path2) + { + // $path could theoretically be empty (e.g. no path is given), in which + // case it shouldn't expand to array(''), which would compare to one's + // root / + $path1 = $path1 ? explode('/', $path1) : array(); + $path2 = $path2 ? explode('/', $path2) : array(); + + $shared = array(); + + // compare paths & strip identical ancestors + foreach ($path1 as $i => $chunk) { + if (isset($path2[$i]) && $path1[$i] == $path2[$i]) { + $shared[] = $chunk; + } else { + break; + } + } + + return implode('/', $shared); + } + + /** + * Convert paths relative from 1 file to another. + * + * E.g. + * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css + * should become: + * ../../core/layout/images/img.gif relative to + * /home/forkcms/frontend/cache/minified_css + * + * @param string $path The relative path that needs to be converted + * + * @return string The new relative path + */ + public function convert($path) + { + // quit early if conversion makes no sense + if ($this->from === $this->to) { + return $path; + } + + $path = $this->normalize($path); + // if we're not dealing with a relative path, just return absolute + if (strpos($path, '/') === 0) { + return $path; + } + + // normalize paths + $path = $this->normalize($this->from.'/'.$path); + + // strip shared ancestor paths + $shared = $this->shared($path, $this->to); + $path = mb_substr($path, mb_strlen($shared)); + $to = mb_substr($this->to, mb_strlen($shared)); + + // add .. for every directory that needs to be traversed to new path + $to = str_repeat('../', count(array_filter(explode('/', $to)))); + + return $to.ltrim($path, '/'); + } + + /** + * Attempt to get the directory name from a path. + * + * @param string $path + * + * @return string + */ + protected function dirname($path) + { + if (@is_file($path)) { + return dirname($path); + } + + if (@is_dir($path)) { + return rtrim($path, '/'); + } + + // no known file/dir, start making assumptions + + // ends in / = dir + if (mb_substr($path, -1) === '/') { + return rtrim($path, '/'); + } + + // has a dot in the name, likely a file + if (preg_match('/.*\..*$/', basename($path)) !== 0) { + return dirname($path); + } + + // you're on your own here! + return $path; + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/ConverterInterface.php b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/ConverterInterface.php new file mode 100644 index 00000000..dc1b7657 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/ConverterInterface.php @@ -0,0 +1,24 @@ +<?php + +namespace MatthiasMullie\PathConverter; + +/** + * Convert file paths. + * + * Please report bugs on https://github.com/matthiasmullie/path-converter/issues + * + * @author Matthias Mullie <pathconverter@mullie.eu> + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +interface ConverterInterface +{ + /** + * Convert file paths. + * + * @param string $path The path to be converted + * + * @return string The new path + */ + public function convert($path); +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/NoConverter.php b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/NoConverter.php new file mode 100644 index 00000000..2fcfd0f2 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/NoConverter.php @@ -0,0 +1,23 @@ +<?php + +namespace MatthiasMullie\PathConverter; + +/** + * Don't convert paths. + * + * Please report bugs on https://github.com/matthiasmullie/path-converter/issues + * + * @author Matthias Mullie <pathconverter@mullie.eu> + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +class NoConverter implements ConverterInterface +{ + /** + * {@inheritdoc} + */ + public function convert($path) + { + return $path; + } +} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/LICENSE b/data/web/inc/lib/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 00000000..45c7017d --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh b/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh new file mode 100755 index 00000000..b4a5ba31 --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/composer.json b/data/web/inc/lib/vendor/paragonie/random_compat/composer.json new file mode 100644 index 00000000..1fa8de9f --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,34 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "polyfill", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "vimeo/psalm": "^1", + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + } +} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 00000000..eb50ebfc --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 00000000..6a1d7f30 --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php b/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php new file mode 100644 index 00000000..c7731a56 --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php @@ -0,0 +1,32 @@ +<?php +/** + * Random_* Compatibility Library + * for using the new PHP 7 random_* API in PHP 5 projects + * + * @version 2.99.99 + * @released 2018-06-06 + * + * The MIT License (MIT) + * + * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises + * + * 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. + */ + +// NOP diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/other/build_phar.php b/data/web/inc/lib/vendor/paragonie/random_compat/other/build_phar.php new file mode 100644 index 00000000..70ef4b2e --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/other/build_phar.php @@ -0,0 +1,57 @@ +<?php +$dist = dirname(__DIR__).'/dist'; +if (!is_dir($dist)) { + mkdir($dist, 0755); +} +if (file_exists($dist.'/random_compat.phar')) { + unlink($dist.'/random_compat.phar'); +} +$phar = new Phar( + $dist.'/random_compat.phar', + FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME, + 'random_compat.phar' +); +rename( + dirname(__DIR__).'/lib/random.php', + dirname(__DIR__).'/lib/index.php' +); +$phar->buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php b/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 00000000..d71d1b81 --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ +<?php + +require_once 'lib/byte_safe_strings.php'; +require_once 'lib/cast_to_int.php'; +require_once 'lib/error_polyfill.php'; +require_once 'other/ide_stubs/libsodium.php'; +require_once 'lib/random.php'; + +$int = random_int(0, 65536); diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/psalm.xml b/data/web/inc/lib/vendor/paragonie/random_compat/psalm.xml new file mode 100644 index 00000000..596d99dd --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/psalm.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<psalm + autoloader="psalm-autoload.php" + stopOnFirstError="false" + useDocblockTypes="true" +> + <projectFiles> + <directory name="lib" /> + </projectFiles> + <issueHandlers> + <RedundantConditionGivenDocblockType errorLevel="info" /> + <UnresolvableInclude errorLevel="info" /> + <DuplicateClass errorLevel="info" /> + <InvalidOperand errorLevel="info" /> + <UndefinedConstant errorLevel="info" /> + <MissingReturnType errorLevel="info" /> + <InvalidReturnType errorLevel="info" /> + </issueHandlers> +</psalm> diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/ISSUE_TEMPLATE.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 97776f1c..00000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1 +0,0 @@ -Non-security issues and pull requests are no longer being accepted for the legacy PHPMailer 5.2 branch. Migrate to PHPMailer 6.0 (or later) and report your issue there. \ No newline at end of file diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/PULL_REQUEST_TEMPLATE.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 97776f1c..00000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1 +0,0 @@ -Non-security issues and pull requests are no longer being accepted for the legacy PHPMailer 5.2 branch. Migrate to PHPMailer 6.0 (or later) and report your issue there. \ No newline at end of file diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/LICENSE b/data/web/inc/lib/vendor/phpmailer/phpmailer/LICENSE index 8e0763d1..f166cc57 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/LICENSE +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/LICENSE @@ -1,8 +1,8 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -10,7 +10,7 @@ as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - + Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a @@ -111,8 +111,8 @@ modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE + + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other @@ -146,7 +146,7 @@ such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. - + 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an @@ -158,7 +158,7 @@ Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - + 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 @@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - + Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. @@ -267,7 +267,7 @@ Library will still fall under Section 6.) distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - + 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work @@ -312,7 +312,7 @@ of these things: from a designated place, offer equivalent access to copy the above specified materials from the same place. - e) verify that the user has already received a copy of these + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the @@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - + 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined @@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - + 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or @@ -422,7 +422,7 @@ conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - + 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is @@ -432,7 +432,7 @@ decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. @@ -455,8 +455,8 @@ FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS - + END OF TERMS AND CONDITIONS + How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest @@ -485,7 +485,7 @@ convey the exclusion of warranty; and each file should have at least the You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. @@ -499,6 +499,4 @@ necessary. Here is a sample; alter the names: <signature of Ty Coon>, 1 April 1990 Ty Coon, President of Vice -That's all there is to it! - - +That's all there is to it! \ No newline at end of file diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION index f0fb1a22..32779c18 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION @@ -1 +1 @@ -5.2.26 +5.2.27 \ No newline at end of file diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php index 99f9092c..12a95875 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php @@ -31,7 +31,7 @@ class PHPMailer * The PHPMailer Version number. * @var string */ - public $Version = '5.2.26'; + public $Version = '5.2.27'; /** * Email priority. @@ -1296,9 +1296,12 @@ class PHPMailer // Sign with DKIM if enabled if (!empty($this->DKIM_domain) - && !empty($this->DKIM_selector) - && (!empty($this->DKIM_private_string) - || (!empty($this->DKIM_private) && file_exists($this->DKIM_private)) + and !empty($this->DKIM_selector) + and (!empty($this->DKIM_private_string) + or (!empty($this->DKIM_private) + and self::isPermittedPath($this->DKIM_private) + and file_exists($this->DKIM_private) + ) ) ) { $header_dkim = $this->DKIM_Add( @@ -1463,6 +1466,18 @@ class PHPMailer return true; } + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * @param string $path A relative or absolute path to a file. + * @return bool + */ + protected static function isPermittedPath($path) + { + return !preg_match('#^[a-z]+://#i', $path); + } + /** * Send mail using the PHP mail() function. * @param string $header The message headers @@ -1791,7 +1806,7 @@ class PHPMailer // There is no English translation file if ($langcode != 'en') { // Make sure language file path is readable - if (!is_readable($lang_file)) { + if (!self::isPermittedPath($lang_file) or !is_readable($lang_file)) { $foundlang = false; } else { // Overwrite language-specific strings. @@ -2499,6 +2514,8 @@ class PHPMailer * Add an attachment from a path on the filesystem. * Never use a user-supplied path to a file! * Returns false if the file could not be found or read. + * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client. + * If you need to do that, fetch the resource yourself and pass it in via a local file or string. * @param string $path Path to the attachment. * @param string $name Overrides the attachment name. * @param string $encoding File encoding (see $Encoding). @@ -2510,7 +2527,7 @@ class PHPMailer public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') { try { - if (!@is_file($path)) { + if (!self::isPermittedPath($path) or !@is_file($path)) { throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); } @@ -2691,7 +2708,7 @@ class PHPMailer protected function encodeFile($path, $encoding = 'base64') { try { - if (!is_readable($path)) { + if (!self::isPermittedPath($path) or !file_exists($path)) { throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); } $magic_quotes = get_magic_quotes_runtime(); @@ -3035,7 +3052,7 @@ class PHPMailer */ public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') { - if (!@is_file($path)) { + if (!self::isPermittedPath($path) or !@is_file($path)) { $this->setError($this->lang('file_access') . $path); return false; } diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php index f833ac61..5a458e5d 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php @@ -34,7 +34,7 @@ class POP3 * @var string * @access public */ - public $Version = '5.2.26'; + public $Version = '5.2.27'; /** * Default POP3 port number. diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php index be6ddce4..118cb20f 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php @@ -30,7 +30,7 @@ class SMTP * The PHPMailer SMTP version number. * @var string */ - const VERSION = '5.2.26'; + const VERSION = '5.2.27'; /** * SMTP line break constant. @@ -81,7 +81,7 @@ class SMTP * @deprecated Use the `VERSION` constant instead * @see SMTP::VERSION */ - public $Version = '5.2.26'; + public $Version = '5.2.27'; /** * SMTP server port number. diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/.travis.yml b/data/web/inc/lib/vendor/yubico/u2flib-server/.travis.yml index b4282b2c..beade3b0 100644 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/.travis.yml +++ b/data/web/inc/lib/vendor/yubico/u2flib-server/.travis.yml @@ -3,6 +3,7 @@ sudo: false php: - 7.0 - 7.1 + - 7.2 - hhvm matrix: include: @@ -15,6 +16,7 @@ before_script: - composer install script: + - ./vendor/bin/psalm - ./vendor/phpunit/phpunit/phpunit -c phpunit.xml after_success: diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/NEWS b/data/web/inc/lib/vendor/yubico/u2flib-server/NEWS index a8f97ae7..496175ed 100644 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/NEWS +++ b/data/web/inc/lib/vendor/yubico/u2flib-server/NEWS @@ -1,5 +1,11 @@ php-u2flib-server NEWS -- History of user-visible changes. +* Version 1.0.2 (released 2018-09-07) + ** Additional error checks. + ** Add user presence check. + ** Support single files for attestation root. + ** Type safety, CSPRNG, avoid chr(). + * Version 1.0.1 (released 2017-05-09) ** Move examples to phps so they don't execute by default ** Use common challenge for multiple registrations diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/composer.json b/data/web/inc/lib/vendor/yubico/u2flib-server/composer.json index 5b3a970d..3f2d9eab 100644 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/composer.json +++ b/data/web/inc/lib/vendor/yubico/u2flib-server/composer.json @@ -5,12 +5,14 @@ "license":"BSD-2-Clause", "require": { "ext-openssl":"*", + "paragonie/random_compat": ">= 1", "php": ">=5.6" }, "autoload": { "classmap": ["src/"] }, "require-dev": { - "phpunit/phpunit": "~5.7" + "phpunit/phpunit": "~5.7", + "vimeo/psalm": "^0|^1|^2" } } diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/psalm.xml b/data/web/inc/lib/vendor/yubico/u2flib-server/psalm.xml new file mode 100644 index 00000000..6b6234cf --- /dev/null +++ b/data/web/inc/lib/vendor/yubico/u2flib-server/psalm.xml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<psalm + totallyTyped="false" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="https://getpsalm.org/schema/config" + xsi:schemaLocation="https://getpsalm.org/schema/config file:///mnt/share/php-u2flib-server/vendor/vimeo/psalm/config.xsd" +> + <projectFiles> + <directory name="src" /> + <ignoreFiles> + <directory name="vendor" /> + </ignoreFiles> + </projectFiles> + + <issueHandlers> + <LessSpecificReturnType errorLevel="info" /> + + <!-- level 3 issues - slightly lazy code writing, but provably low false-negatives --> + + <DeprecatedMethod errorLevel="info" /> + <DeprecatedProperty errorLevel="info" /> + <DeprecatedClass errorLevel="info" /> + <DeprecatedInterface errorLevel="info" /> + + <MissingClosureReturnType errorLevel="info" /> + <MissingReturnType errorLevel="info" /> + <MissingPropertyType errorLevel="info" /> + <InvalidDocblock errorLevel="info" /> + <MisplacedRequiredParam errorLevel="info" /> + + <PropertyNotSetInConstructor errorLevel="info" /> + <MissingConstructor errorLevel="info" /> + <MissingClosureParamType errorLevel="info" /> + <MissingParamType errorLevel="info" /> + + <RedundantCondition errorLevel="info" /> + + <DocblockTypeContradiction errorLevel="suppress" /> + <RedundantConditionGivenDocblockType errorLevel="suppress" /> + + <UnresolvableInclude errorLevel="info" /> + + <RawObjectIteration errorLevel="info" /> + + <!-- psalm seems to wrongly complain about this, set the errorLevel to info for now --> + <UndefinedConstant errorLevel="info" /> + </issueHandlers> +</psalm> diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php b/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php index a11c78fb..8583fff2 100644 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php +++ b/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php @@ -32,6 +32,12 @@ namespace u2flib_server; /** Constant for the version of the u2f protocol */ const U2F_VERSION = "U2F_V2"; +/** Constant for the type value in registration clientData */ +const REQUEST_TYPE_REGISTER = "navigator.id.finishEnrollment"; + +/** Constant for the type value in authentication clientData */ +const REQUEST_TYPE_AUTHENTICATE = "navigator.id.getAssertion"; + /** Error for the authentication message not matching any outstanding * authentication request */ const ERR_NO_MATCHING_REQUEST = 1; @@ -69,6 +75,15 @@ const ERR_BAD_UA_RETURNING = 10; /** Error old OpenSSL version */ const ERR_OLD_OPENSSL = 11; +/** Error for the origin not matching the appId */ +const ERR_NO_MATCHING_ORIGIN = 12; + +/** Error for the type in clientData being invalid */ +const ERR_BAD_TYPE = 13; + +/** Error for bad user presence byte value */ +const ERR_BAD_USER_PRESENCE = 14; + /** @internal */ const PUBKEY_LEN = 65; @@ -160,6 +175,14 @@ class U2F throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE ); } + if(isset($cli->typ) && $cli->typ !== REQUEST_TYPE_REGISTER) { + throw new Error('ClientData type is invalid', ERR_BAD_TYPE); + } + + if(isset($cli->origin) && $cli->origin !== $request->appId) { + throw new Error('App ID does not match the origin', ERR_NO_MATCHING_ORIGIN); + } + $registration = new Registration(); $offs = 1; $pubKey = substr($rawReg, $offs, PUBKEY_LEN); @@ -199,7 +222,7 @@ class U2F } $signature = substr($rawReg, $offs); - $dataToVerify = chr(0); + $dataToVerify = pack('C', 0); $dataToVerify .= hash('sha256', $request->appId, true); $dataToVerify .= hash('sha256', $clientData, true); $dataToVerify .= $kh; @@ -227,6 +250,7 @@ class U2F if( !is_object( $reg ) ) { throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.'); } + /** @var Registration $reg */ $sig = new SignRequest(); $sig->appId = $this->appId; @@ -269,6 +293,11 @@ class U2F $clientData = $this->base64u_decode($response->clientData); $decodedClient = json_decode($clientData); + + if(isset($decodedClient->typ) && $decodedClient->typ !== REQUEST_TYPE_AUTHENTICATE) { + throw new Error('ClientData type is invalid', ERR_BAD_TYPE); + } + foreach ($requests as $req) { if( !is_object( $req ) ) { throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.'); @@ -283,6 +312,9 @@ class U2F if($req === null) { throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST ); } + if(isset($decodedClient->origin) && $decodedClient->origin !== $req->appId) { + throw new Error('App ID does not match the origin', ERR_NO_MATCHING_ORIGIN); + } foreach ($registrations as $reg) { if( !is_object( $reg ) ) { throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.'); @@ -308,12 +340,16 @@ class U2F $signature = substr($signData, 5); if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) { + $upb = unpack("Cupb", substr($signData, 0, 1)); + if($upb['upb'] !== 1) { + throw new Error('User presence byte value is invalid', ERR_BAD_USER_PRESENCE ); + } $ctr = unpack("Nctr", substr($signData, 1, 4)); $counter = $ctr['ctr']; /* TODO: wrap-around should be handled somehow.. */ if($counter > $reg->counter) { $reg->counter = $counter; - return $reg; + return self::castObjectToRegistration($reg); } else { throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW ); } @@ -322,6 +358,28 @@ class U2F } } + /** + * @param object $object + * @return Registration + */ + protected static function castObjectToRegistration($object) + { + $reg = new Registration(); + if (property_exists($object, 'publicKey')) { + $reg->publicKey = $object->publicKey; + } + if (property_exists($object, 'certificate')) { + $reg->certificate = $object->certificate; + } + if (property_exists($object, 'counter')) { + $reg->counter = $object->counter; + } + if (property_exists($object, 'keyHandle')) { + $reg->keyHandle = $object->keyHandle; + } + return $reg; + } + /** * @return array */ @@ -329,13 +387,15 @@ class U2F { $files = array(); $dir = $this->attestDir; - if($dir && $handle = opendir($dir)) { + if($dir !== null && is_dir($dir) && $handle = opendir($dir)) { while(false !== ($entry = readdir($handle))) { if(is_file("$dir/$entry")) { $files[] = "$dir/$entry"; } } closedir($handle); + } elseif (is_file("$dir")) { + $files[] = "$dir"; } return $files; } @@ -395,11 +455,7 @@ class U2F */ private function createChallenge() { - $challenge = openssl_random_pseudo_bytes(32, $crypto_strong ); - if( $crypto_strong !== true ) { - throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM); - } - + $challenge = random_bytes(32); $challenge = $this->base64u_encode( $challenge ); return $challenge; @@ -413,7 +469,7 @@ class U2F */ private function fixSignatureUnusedBits($cert) { - if(in_array(hash('sha256', $cert), $this->FIXCERTS)) { + if(in_array(hash('sha256', $cert), $this->FIXCERTS, true)) { $cert[strlen($cert) - 257] = "\0"; } return $cert; @@ -427,13 +483,13 @@ class U2F */ class RegisterRequest { - /** Protocol version */ + /** @var string Protocol version */ public $version = U2F_VERSION; - /** Registration challenge */ + /** @var string Registration challenge */ public $challenge; - /** Application id */ + /** @var string Application id */ public $appId; /** @@ -455,17 +511,17 @@ class RegisterRequest */ class SignRequest { - /** Protocol version */ + /** @var string Protocol version */ public $version = U2F_VERSION; - /** Authentication challenge */ - public $challenge; + /** @var string Authentication challenge */ + public $challenge = ''; - /** Key handle of a registered authenticator */ - public $keyHandle; + /** @var string Key handle of a registered authenticator */ + public $keyHandle = ''; - /** Application id */ - public $appId; + /** @var string Application id */ + public $appId = ''; } /** @@ -475,16 +531,16 @@ class SignRequest */ class Registration { - /** The key handle of the registered authenticator */ - public $keyHandle; + /** @var string The key handle of the registered authenticator */ + public $keyHandle = ''; - /** The public key of the registered authenticator */ - public $publicKey; + /** @var string The public key of the registered authenticator */ + public $publicKey = ''; - /** The attestation certificate of the registered authenticator */ - public $certificate; + /** @var string The attestation certificate of the registered authenticator */ + public $certificate = ''; - /** The counter associated with this registration */ + /** @var int The counter associated with this registration */ public $counter = -1; } diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/tests/certs/yubico-u2f-ca-1.pem b/data/web/inc/lib/vendor/yubico/u2flib-server/tests/certs/yubico-u2f-ca-1.pem deleted file mode 100644 index 15a1dc28..00000000 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/tests/certs/yubico-u2f-ca-1.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ -dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw -MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290 -IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk -5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep -8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw -nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT -9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw -LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ -hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN -BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4 -MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt -hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k -LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U -sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc -U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== ------END CERTIFICATE----- diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/tests/u2flib_test.php b/data/web/inc/lib/vendor/yubico/u2flib-server/tests/u2flib_test.php deleted file mode 100644 index 56af62bf..00000000 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/tests/u2flib_test.php +++ /dev/null @@ -1,296 +0,0 @@ -<?php -/** - * Copyright (c) 2014 Yubico AB - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -require_once(__DIR__ . '/../src/u2flib_server/U2F.php'); - -class U2FTest extends \PHPUnit_Framework_TestCase { - /** @var u2flib_server\U2F */ - private $u2f; - - public function setUp() { - $this->u2f = new u2flib_server\U2F("http://demo.example.com"); - } - - public function testGetRegisterData() { - list($reg, $signData) = $this->u2f->getRegisterData(); - $this->assertJsonStringEqualsJsonString(json_encode(array()), json_encode($signData)); - $this->assertEquals('U2F_V2', $reg->version); - $this->assertObjectHasAttribute('challenge', $reg); - $this->assertEquals('http://demo.example.com', $reg->appId); - } - - public function testDoRegister() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9", "errorCode": 0 }'); - $reg = $this->u2f->doRegister($req, $resp); - $this->assertEquals('CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w', $reg->keyHandle); - $this->assertEquals('BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y/yaFORPUe3c=', $reg->publicKey); - $this->assertEquals('MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp/VRZHOwd2NZNzpnB9ePNKvUaWCGK/gN+cynnYFdwJ75iSgMVYb/RnFcdPwnsBzBU68hbhTnu/FvJxWo7rZJ2q7qXpA10eLVXJr4/4oSXEk9I/0IIHqOP98Ck/fAoI5gYI7ygndyqoPJ/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh/h7oKEKamCWk19dJp5jHQmumkHlvQhH/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg/0J+xOb4zl6a1z65nae4OTj7628/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==', $reg->certificate); - $this->assertLessThan(0, $reg->counter); - } - - public function testDoRegisterNoCert() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $reg = $this->u2f->doRegister($req, $resp, false); - $this->assertEquals('CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w', $reg->keyHandle); - $this->assertEquals('BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y/yaFORPUe3c=', $reg->publicKey); - $this->assertEquals('', $reg->certificate); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_ATTESTATION_VERIFICATION - */ - public function testDoRegisterAttestFail() { - $this->u2f = new u2flib_server\U2F("http://demo.example.com", __DIR__ . "/../tests/certs"); - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp, true); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_ATTESTATION_SIGNATURE - */ - public function testDoRegisterFail2() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8NwW=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp, false); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_UNMATCHED_CHALLENGE - */ - public function testDoRegisterFail() { - $req = json_decode('{"version":"U2F_V2","challenge":"YKA0X075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp, false); - } - - public function testDoRegisterAttest() { - $this->u2f = new u2flib_server\U2F("http://demo.example.com", __DIR__ . "/../tests/certs"); - $req = json_decode('{"version":"U2F_V2","challenge":"5CBRhGBb2CXSum71GNREBGft7yz9g1jZO7JTkHGFsVY","appId":"http:\/\/demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQRX1gfcG-ofTlk9rjB9spsIMrmT9ba0DLto5fzk8FDB05ModNU2sWAqoQRemYiUrILQdbNGpN_aHA0_oq8kcd_XQCK-Ut0PWaOtz43t0aAV04U788e-dvpeqLtHxtINjgmutKM8_GJQ7F-3W0dogUjSANuRYRdkkSEHPcVdLSkpyfowggIbMIIBBaADAgECAgRAxBIlMAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTA4NjU5MTUyNTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABK2iSVV7KGNEdPE-oHGvobNnHVw6ZZ6vB3jNIYB1C4t32OucHzMweHqM5CAMSMDHtfp1vuJYaiQSk7jb6M48WtejEjAQMA4GCisGAQQBgsQKAQEEADALBgkqhkiG9w0BAQsDggEBAVg0BoEHEEp4LJLYPYFACRGS8WZiXkCA8crYLgGnzvfKXwPwyKJlUzYxxv5xoRrl5zjkIUXhZ4mnHZVsnj9EY_VGDuRRzKX7YtxTZpFZn7ej3abjLhckTkkQ_AhUkmP7VuK2AWLgYsS8ejGUqughBsKvh_84uxTAEr5BS-OGg2yi7UIjd8W0nOCc6EN8d_8wCiPOjt2Y_-TKpLLTXKszk4UnWNzRdxBThmBBprJBZbF1VyVRvJm5yRLBpth3G8KMvrt4Nu3Ecoj_Q154IJpWe1Dp1upDFLOG9nWCRQk25Y264k9BDISfqs-wHvUjIo2iDnKl5UVoauTWaT7M6KuEwl4wRAIgYUVjS_yTwJAtF35glSbf9Et-5tJzlHOeAqmbACd6pwsCIE0MkTR5XNQoO4XqDaUZCXmadWu8yU1gfE7AJI9JUUcc", "clientData": "eyAiY2hhbGxlbmdlIjogIjVDQlJoR0JiMkNYU3VtNzFHTlJFQkdmdDd5ejlnMWpaTzdKVGtIR0ZzVlkiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $reg = $this->u2f->doRegister($req, $resp, true); - $this->assertEquals('Ir5S3Q9Zo63Pje3RoBXThTvzx752-l6ou0fG0g2OCa60ozz8YlDsX7dbR2iBSNIA25FhF2SRIQc9xV0tKSnJ-g', $reg->keyHandle); - $this->assertEquals('BFfWB9wb6h9OWT2uMH2ymwgyuZP1trQMu2jl/OTwUMHTkyh01TaxYCqhBF6ZiJSsgtB1s0ak39ocDT+iryRx39c=', $reg->publicKey); - $this->assertEquals('MIICGzCCAQWgAwIBAgIEQMQSJTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDEwODY1OTE1MjUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAStoklVeyhjRHTxPqBxr6GzZx1cOmWerwd4zSGAdQuLd9jrnB8zMHh6jOQgDEjAx7X6db7iWGokEpO42+jOPFrXoxIwEDAOBgorBgEEAYLECgEBBAAwCwYJKoZIhvcNAQELA4IBAQBYNAaBBxBKeCyS2D2BQAkRkvFmYl5AgPHK2C4Bp873yl8D8MiiZVM2Mcb+caEa5ec45CFF4WeJpx2VbJ4/RGP1Rg7kUcyl+2LcU2aRWZ+3o92m4y4XJE5JEPwIVJJj+1bitgFi4GLEvHoxlKroIQbCr4f/OLsUwBK+QUvjhoNsou1CI3fFtJzgnOhDfHf/MAojzo7dmP/kyqSy01yrM5OFJ1jc0XcQU4ZgQaayQWWxdVclUbyZuckSwabYdxvCjL67eDbtxHKI/0NeeCCaVntQ6dbqQxSzhvZ1gkUJNuWNuuJPQQyEn6rPsB71IyKNog5ypeVFaGrk1mk+zOirhMJe', $reg->certificate); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_PUBKEY_DECODE - */ - public function testDoRegisterBadKeyInCert() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABdsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_PUBKEY_DECODE - */ - public function testDoRegisterBadKey() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQMtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $request of doRegister() method only accepts object. - */ - public function testDoRegisterInvalidRequest() { - $req = 'request'; - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $response of doRegister() method only accepts object. - */ - public function testDoRegisterInvalidResponse() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = 'response'; - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_BAD_UA_RETURNING - */ - public function testDoRegisterUAError() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{"errorCode": "4"}'); - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $include_cert of doRegister() method only accepts boolean. - */ - public function testDoRegisterInvalidInclude_cert() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp, 'bar'); - } - - public function testGetAuthenticateData() { - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=="}')); - $data = $this->u2f->getAuthenticateData($regs); - $inst = $data[0]; - $this->assertEquals("U2F_V2", $inst->version); - $this->assertObjectHasAttribute("challenge", $inst); - $this->assertEquals('CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w', $inst->keyHandle); - $this->assertEquals('http://demo.example.com', $inst->appId); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $registrations of getAuthenticateData() method only accepts array of object. - */ - public function testGetAuthenticateDataInvalidRegistrations2() { - $regs = array('YubiKey NEO', 'YubiKey Standard'); - $data = $this->u2f->getAuthenticateData($regs); - } - - public function testDoAuthenticate() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w", "errorCode": 0 }'); - $data = $this->u2f->doAuthenticate($reqs, $regs, $resp); - $this->assertEquals(4, $data->counter); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_COUNTER_TOO_LOW - */ - public function testDoAuthenticateCtrFail() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":5}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_AUTHENTICATION_FAILURE - */ - public function testDoAuthenticateFail() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=="}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAnG==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_NO_MATCHING_REQUEST - */ - public function testDoAuthenticateWrongReq() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"cTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=="}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_NO_MATCHING_REGISTRATION - */ - public function testDoAuthenticateWrongReg() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"cTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=="}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_PUBKEY_DECODE - */ - public function testDoAuthenticateBadKey() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"bC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $requests of doAuthenticate() method only accepts array of object. - */ - public function testDoAuthenticateInvalidRequests2() { - $reqs = array('YubiKey NEO', 'YubiKey Standard'); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $registrations of doAuthenticate() method only accepts array of object. - */ - public function testDoAuthenticateInvalidRegistrations2() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array('YubiKey NEO', 'YubiKey Standard'); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $response of doAuthenticate() method only accepts object. - */ - public function testDoAuthenticateInvalidResponse() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = 'Response'; - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_BAD_UA_RETURNING - */ - public function testDoAuthenticateUAError() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = json_decode('{"errorCode": "5"}'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } -} - -?> diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index a5e03672..7c651803 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -1,9 +1,11 @@ <?php require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; $default_autodiscover_config = $autodiscover_config; + if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) { include_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php'; } +unset($https_port); $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config); header_remove("X-Powered-By"); @@ -17,9 +19,25 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php'; // Load Sieve require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/sieve/SieveParser.php'; +// Minify JS +use MatthiasMullie\Minify; +$js_minifier = new Minify\JS(); +$js_dir = array_diff(scandir('/web/js/build'), array('..', '.')); +foreach ($js_dir as $js_file) { + $js_minifier->add('/web/js/build/' . $js_file); +} + +// Minify CSS +$css_minifier = new Minify\CSS(); +$css_dir = array_diff(scandir('/web/css/build'), array('..', '.')); +foreach ($css_dir as $css_file) { + $css_minifier->add('/web/css/build/' . $css_file); +} + // U2F API + T/HOTP API $u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']); -$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL); +$qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider(); +$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider); // Redis $redis = new Redis(); @@ -27,31 +45,40 @@ $redis->connect('redis-mailcow', 6379); // PDO // Calculate offset -$now = new DateTime(); -$mins = $now->getOffset() / 60; -$sgn = ($mins < 0 ? -1 : 1); -$mins = abs($mins); -$hrs = floor($mins / 60); -$mins -= $hrs * 60; -$offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); +// $now = new DateTime(); +// $mins = $now->getOffset() / 60; +// $sgn = ($mins < 0 ? -1 : 1); +// $mins = abs($mins); +// $hrs = floor($mins / 60); +// $mins -= $hrs * 60; +// $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); -$dsn = $database_type . ":host=" . $database_host . ";dbname=" . $database_name; +$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, - PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", + //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { +// Stop when SQL connection fails ?> -<center style='font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;'>Connection to database failed.<br /><br />The following error was reported:<br/> <?=$e->getMessage();?></center> +<center style='font-family:sans-serif;'>Connection to database failed.<br /><br />The following error was reported:<br/> <?=$e->getMessage();?></center> <?php exit; } -function pdo_exception_handler($e) { +// Stop when dockerapi is not available +if (fsockopen("tcp://dockerapi", 443, $errno, $errstr) === false) { +?> +<center style='font-family:sans-serif;'>Connection to dockerapi container failed.<br /><br />The following error was reported:<br/><?=$errno;?> - <?=$errstr;?></center> +<?php +exit; +} + +function exception_handler($e) { if ($e instanceof PDOException) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -64,12 +91,12 @@ function pdo_exception_handler($e) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => array('mysql_error', 'unknown error') + 'msg' => 'An unknown error occured: ' . print_r($e, true) ); return false; } } -set_exception_handler('pdo_exception_handler'); +set_exception_handler('exception_handler'); // TODO: Move function function get_remote_ip($anonymize = null) { @@ -99,14 +126,16 @@ function get_remote_ip($anonymize = null) { require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; +// IMAP lib +// use Ddeboer\Imap\Server; +// $imap_server = new Server('dovecot', 143, '/imap/tls/novalidate-cert'); + // Set language if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) { if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $header_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); - foreach ($AVAILABLE_LANGUAGES as $available_lang) { - if ($header_lang == $available_lang) { - $_SESSION['mailcow_locale'] = strtolower(trim($header_lang)); - } + $header_lang = strtolower(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2)); + if (in_array($header_lang, $AVAILABLE_LANGUAGES)) { + $_SESSION['mailcow_locale'] = $header_lang; } } else { @@ -114,7 +143,7 @@ if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) { } } if (isset($_COOKIE['mailcow_locale'])) { - $_SESSION['mailcow_locale'] = $_COOKIE['mailcow_locale']; + (preg_match('/^[a-z]{2}$/', $_COOKIE['mailcow_locale'])) ? $_SESSION['mailcow_locale'] = $_COOKIE['mailcow_locale'] : setcookie("mailcow_locale", "", time() - 300); } if (isset($_GET['lang']) && in_array($_GET['lang'], $AVAILABLE_LANGUAGES)) { $_SESSION['mailcow_locale'] = $_GET['lang']; @@ -124,23 +153,34 @@ if (isset($_GET['lang']) && in_array($_GET['lang'], $AVAILABLE_LANGUAGES)) { require_once $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.php'; include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.admin.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quarantine.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quota_notification.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.transports.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php'; init_db_schema(); if (isset($_SESSION['mailcow_cc_role'])) { - set_acl(); + // if ($_SESSION['mailcow_cc_role'] == 'user') { + // list($master_user, $master_passwd) = explode(':', file_get_contents('/etc/sogo/sieve.creds')); + // $imap_connection = $imap_server->authenticate($_SESSION['mailcow_cc_username'] . '*' . trim($master_user), trim($master_passwd)); + // $master_user = $master_passwd = null; + // } + acl('to_session'); } $UI_TEXTS = customize('get', 'ui_texts'); + diff --git a/data/web/inc/sessions.inc.php b/data/web/inc/sessions.inc.php index a3b81407..ddc997d1 100644 --- a/data/web/inc/sessions.inc.php +++ b/data/web/inc/sessions.inc.php @@ -1,22 +1,31 @@ <?php // Start session -ini_set("session.cookie_httponly", 1); -ini_set('session.gc_maxlifetime', $SESSION_LIFETIME); +if (session_status() !== PHP_SESSION_ACTIVE) { + ini_set("session.cookie_httponly", 1); + ini_set('session.gc_maxlifetime', $SESSION_LIFETIME); +} if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") { - ini_set("session.cookie_secure", 1); + if (session_status() !== PHP_SESSION_ACTIVE) { + ini_set("session.cookie_secure", 1); + } $IS_HTTPS = true; } elseif (isset($_SERVER['HTTPS'])) { - ini_set("session.cookie_secure", 1); + if (session_status() !== PHP_SESSION_ACTIVE) { + ini_set("session.cookie_secure", 1); + } $IS_HTTPS = true; } else { $IS_HTTPS = false; } // session_set_cookie_params($SESSION_LIFETIME, '/', '', $IS_HTTPS, true); -session_start(); +if (session_status() !== PHP_SESSION_ACTIVE) { + session_start(); +} + if (!isset($_SESSION['CSRF']['TOKEN'])) { $_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32)); } @@ -28,27 +37,65 @@ if (!isset($_SESSION['SESS_REMOTE_UA'])) { // API if (!empty($_SERVER['HTTP_X_API_KEY'])) { - $stmt = $pdo->prepare("SELECT `username`, `allow_from` FROM `api` WHERE `api_key` = :api_key AND `active` = '1';"); + $stmt = $pdo->prepare("SELECT `allow_from` FROM `api` WHERE `api_key` = :api_key AND `active` = '1';"); $stmt->execute(array( - ':api_key' => preg_replace('/[^A-Z0-9-]/i', '', $_SERVER['HTTP_X_API_KEY']) + ':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY']) )); $api_return = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($api_return['username'])) { + if (!empty($api_return['allow_from'])) { $remote = get_remote_ip(false); $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from'])); if (in_array($remote, $allow_from)) { - $_SESSION['mailcow_cc_username'] = $api_return['username']; + $_SESSION['mailcow_cc_username'] = 'API'; $_SESSION['mailcow_cc_role'] = 'admin'; $_SESSION['mailcow_cc_api'] = true; } + else { + $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); + error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + echo json_encode(array( + 'type' => 'error', + 'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR'] + )); + unset($_POST); + die(); + } + } + else { + $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); + error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + echo json_encode(array( + 'type' => 'error', + 'msg' => 'authentication failed' + )); + unset($_POST); + die(); } } // Update session cookie // setcookie(session_name() ,session_id(), time() + $SESSION_LIFETIME); +// Handle logouts +if (isset($_POST["logout"])) { + if (isset($_SESSION["dual-login"])) { + $_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"]; + $_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"]; + unset($_SESSION["dual-login"]); + header("Location: /mailbox"); + exit(); + } + else { + session_regenerate_id(true); + session_unset(); + session_destroy(); + session_write_close(); + header("Location: /"); + } +} + // Check session function session_check() { - if ($_SESSION['mailcow_cc_api'] === true) { + if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) { return true; } if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) { @@ -77,21 +124,3 @@ if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) { $_POST = array(); $_FILES = array(); } - -// Handle logouts -if (isset($_POST["logout"])) { - if (isset($_SESSION["dual-login"])) { - $_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"]; - $_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"]; - unset($_SESSION["dual-login"]); - header("Location: /mailbox.php"); - exit(); - } - else { - session_regenerate_id(true); - session_unset(); - session_destroy(); - session_write_close(); - header("Location: /"); - } -} diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index 4df1beca..9b622613 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -6,10 +6,18 @@ if (isset($_POST["verify_tfa_login"])) { unset($_SESSION['pending_mailcow_cc_username']); unset($_SESSION['pending_mailcow_cc_role']); unset($_SESSION['pending_tfa_method']); - header("Location: /user.php"); + header("Location: /user"); } } +if (isset($_POST["quick_release"])) { + quarantine('quick_release', $_POST["quick_release"]); +} + +if (isset($_POST["quick_delete"])) { + quarantine('quick_delete', $_POST["quick_delete"]); +} + if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $login_user = strtolower(trim($_POST["login_user"])); $as = check_login($login_user, $_POST["pass_user"]); @@ -17,19 +25,19 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $_SESSION['mailcow_cc_username'] = $login_user; $_SESSION['mailcow_cc_role'] = "admin"; $_SESSION['mailcow_cc_last_login'] = last_login($login_user); - header("Location: /admin.php"); + header("Location: /admin"); } elseif ($as == "domainadmin") { $_SESSION['mailcow_cc_username'] = $login_user; $_SESSION['mailcow_cc_role'] = "domainadmin"; $_SESSION['mailcow_cc_last_login'] = last_login($login_user); - header("Location: /mailbox.php"); + header("Location: /mailbox"); } elseif ($as == "user") { $_SESSION['mailcow_cc_username'] = $login_user; $_SESSION['mailcow_cc_role'] = "user"; $_SESSION['mailcow_cc_last_login'] = last_login($login_user); - header("Location: /user.php"); + header("Location: /user"); } elseif ($as != "pending") { unset($_SESSION['pending_mailcow_cc_username']); @@ -40,7 +48,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { } } -if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { +if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['acl']['login_as'] == "1") { if (isset($_GET["duallogin"])) { $duallogin = html_entity_decode(rawurldecode($_GET["duallogin"])); if (filter_var($duallogin, FILTER_VALIDATE_EMAIL)) { @@ -49,7 +57,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi $_SESSION["dual-login"]["role"] = $_SESSION['mailcow_cc_role']; $_SESSION['mailcow_cc_username'] = $duallogin; $_SESSION['mailcow_cc_role'] = "user"; - header("Location: /user.php"); + header("Location: /user"); } } else { @@ -58,7 +66,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi $_SESSION["dual-login"]["role"] = $_SESSION['mailcow_cc_role']; $_SESSION['mailcow_cc_username'] = $duallogin; $_SESSION['mailcow_cc_role'] = "domainadmin"; - header("Location: /user.php"); + header("Location: /user"); } } } @@ -93,5 +101,8 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi if (isset($_POST["rspamd_ui"])) { rspamd_ui('edit', $_POST); } + if (isset($_POST["mass_send"])) { + sys_mail($_POST); + } } ?> diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 4ac0df47..795fb3f3 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -9,6 +9,7 @@ This file will be reset on upgrades. // SQL database connection variables $database_type = 'mysql'; +$database_sock = '/var/run/mysqld/mysqld.sock'; $database_host = 'mysql'; $database_user = getenv('DBUSER'); $database_pass = getenv('DBPASS'); @@ -26,6 +27,7 @@ if ($https_port === FALSE) { } else { $https_port = substr($_SERVER['HTTP_HOST'], $https_port+1); } + // Alternatively select port here => //$https_port = 1234; // Other settings => @@ -41,18 +43,18 @@ $autodiscover_config = array( // The autoconfig service will additionally announce the STARTTLS-enabled ports, specified in the "tlsport" variable. 'imap' => array( 'server' => $mailcow_hostname, - 'port' => array_pop(explode(':', getenv('IMAPS_PORT'))), - 'tlsport' => array_pop(explode(':', getenv('IMAP_PORT'))), + 'port' => end(explode(':', getenv('IMAPS_PORT'))), + 'tlsport' => end(explode(':', getenv('IMAP_PORT'))), ), 'pop3' => array( 'server' => $mailcow_hostname, - 'port' => array_pop(explode(':', getenv('POPS_PORT'))), - 'tlsport' => array_pop(explode(':', getenv('POP_PORT'))), + 'port' => end(explode(':', getenv('POPS_PORT'))), + 'tlsport' => end(explode(':', getenv('POP_PORT'))), ), 'smtp' => array( 'server' => $mailcow_hostname, - 'port' => array_pop(explode(':', getenv('SMTPS_PORT'))), - 'tlsport' => array_pop(explode(':', getenv('SUBMISSION_PORT'))), + 'port' => end(explode(':', getenv('SMTPS_PORT'))), + 'tlsport' => end(explode(':', getenv('SUBMISSION_PORT'))), ), 'activesync' => array( 'url' => 'https://'.$mailcow_hostname.($https_port == 443 ? '' : ':'.$https_port).'/Microsoft-Server-ActiveSync', @@ -66,17 +68,16 @@ $autodiscover_config = array( 'port' => $https_port, ), ); -unset($https_port); // If false, we will use DEFAULT_LANG // Uses HTTP_ACCEPT_LANGUAGE header $DETECT_LANGUAGE = true; -// Change default language, "de", "en", "es", "nl", "pt", "ru" +// Change default language, "cs", "de", "en", "es", "nl", "pt", "ru" $DEFAULT_LANG = 'en'; // Available languages -$AVAILABLE_LANGUAGES = array('de', 'en', 'es', 'fr', 'lv', 'nl', 'pl', 'pt', 'ru', 'it', 'ca'); +$AVAILABLE_LANGUAGES = array('cs', 'de', 'en', 'es', 'fr', 'lv', 'nl', 'pl', 'pt', 'ru', 'it', 'ca'); // Change theme (default: lumen) // Needs to be one of those: cerulean, cosmo, cyborg, darkly, flatly, journal, lumen, paper, readable, sandstone, @@ -86,7 +87,12 @@ $AVAILABLE_LANGUAGES = array('de', 'en', 'es', 'fr', 'lv', 'nl', 'pl', 'pt', 'ru $DEFAULT_THEME = 'lumen'; // Password complexity as regular expression -$PASSWD_REGEP = '.{4,}'; +// Min. 6 characters +$PASSWD_REGEP = '.{6,}'; +// Min. 6 characters, which must include at least one uppercase letter, one lowercase letter and one number +// $PASSWD_REGEP = '^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{6,}$'; +// Min. 6 characters, which must include at least one letter and one number +// $PASSWD_REGEP = '^(?=.*[0-9])(?=.*[A-Za-z]).{6,}$'; // Show DKIM private keys - false by default $SHOW_DKIM_PRIV_KEYS = false; @@ -122,3 +128,25 @@ $DOCKER_TIMEOUT = 60; // Anonymize IPs logged via UI $ANONYMIZE_IPS = true; + +// MAILBOX_DEFAULT_ATTRIBUTES define default attributes for new mailboxes +// These settings will not change existing mailboxes + +// Force incoming TLS for new mailboxes by default +$MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'] = false; + +// Force outgoing TLS for new mailboxes by default +$MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'] = false; + +// Force password change on next login (only allows login to mailcow UI) +$MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false; + +// Enable SOGo access (set to false to disable access by default) +$MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true; + +// Send notification when quarantine is not empty (never, hourly, daily, weekly) +$MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'] = 'never'; + +// Default mailbox format, should not be changed unless you know exactly, what you do, keep the trailing ":" +// Check dovecot.conf for further changes (e.g. shared namespace) +$MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format'] = 'maildir:'; diff --git a/data/web/index.php b/data/web/index.php index 47339923..19372351 100644 --- a/data/web/index.php +++ b/data/web/index.php @@ -1,19 +1,20 @@ <?php -require_once 'inc/prerequisites.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') { - header('Location: /admin.php'); + header('Location: /admin'); exit(); } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') { - header('Location: /mailbox.php'); + header('Location: /mailbox'); exit(); } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { - header('Location: /user.php'); + header('Location: /user'); exit(); } -require_once 'inc/header.inc.php'; + +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; ?> @@ -107,6 +108,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; </div> </div> </div><!-- /.container --> -<script src="js/index.js"></script> <?php -require_once 'inc/footer.inc.php'; +$js_minifier->add('/web/js/site/index.js'); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; diff --git a/data/web/js/add.js b/data/web/js/add.js deleted file mode 100644 index cbc1d3f1..00000000 --- a/data/web/js/add.js +++ /dev/null @@ -1,23 +0,0 @@ -$(document).ready(function() { - // Auto-fill domain quota when adding new domain - auto_fill_quota = function(domain) { - $.get("/api/v1/get/domain/" + domain, function(data){ - var result = $.parseJSON(JSON.stringify(data)); - max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576); - if (max_new_mailbox_quota != '0') { - $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); - $('#addInputQuota').attr({"disabled": false, "value": "", "type": "number", "max": max_new_mailbox_quota}); - $('#addInputQuota').val(max_new_mailbox_quota); - } - else { - $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); - $('#addInputQuota').attr({"disabled": true, "value": "", "type": "text", "value": "n/a"}); - $('#addInputQuota').val(max_new_mailbox_quota); - } - }); - } - $('#addSelectDomain').on('change', function() { - auto_fill_quota($('#addSelectDomain').val()); - }); - auto_fill_quota($('#addSelectDomain').val()); -}); diff --git a/data/web/js/admin.js b/data/web/js/admin.js deleted file mode 100644 index 24725f60..00000000 --- a/data/web/js/admin.js +++ /dev/null @@ -1,226 +0,0 @@ -// Base64 functions -var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; -jQuery(function($){ - // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery - var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; - function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} - function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} - $("#import_dkim_legend").on('click', function(e) { - e.preventDefault(); - $('#import_dkim_arrow').toggleClass("animation"); - }); - $("#duplicate_dkim_legend").on('click', function(e) { - e.preventDefault(); - $('#duplicate_dkim_arrow').toggleClass("animation"); - }); - $("#rspamd_preset_1").on('click', function(e) { - e.preventDefault(); - $("form[data-id=rsetting]").find("#desc").val(lang.rsettings_preset_1); - $("form[data-id=rsetting]").find("#content").val('priority = 10;\nauthenticated = yes;\napply "default" {\n symbols_enabled = ["DKIM_SIGNED", "RATELIMIT_UPDATE", "RATELIMIT_CHECK", "DYN_RL_CHECK", "HISTORY_SAVE", "MILTER_HEADERS", "ARC_SIGNED"];\n}'); - }); - $("#rspamd_preset_2").on('click', function(e) { - e.preventDefault(); - $("form[data-id=rsetting]").find("#desc").val(lang.rsettings_preset_2); - $("form[data-id=rsetting]").find("#content").val('priority = 10;\nrcpt = "/postmaster@.*/";\nwant_spam = yes;'); - }); - $("#dkim_missing_keys").on('click', function(e) { - e.preventDefault(); - var domains = []; - $('.dkim_missing').each(function() { - domains.push($(this).val()); - }); - $('#dkim_add_domains').val(domains); - }); - function draw_domain_admins() { - ft_domainadmins = FooTable.init('#domainadminstable', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, - {"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}}, - {"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"}, - {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}}, - {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/domain-admin/all', - jsonp: false, - error: function () { - console.log('Cannot draw domain admin table'); - }, - success: function (data) { - return process_table_data(data, 'domainadminstable'); - } - }), - "empty": lang.empty, - "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, - "filtering": {"enabled": true,"delay": 1,"position": "left","connectors": false,"placeholder": lang.filter_table - }, - "sorting": {"enabled": true} - }); - } - function draw_fwd_hosts() { - ft_forwardinghoststable = FooTable.init('#forwardinghoststable', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, - {"name":"host","type":"text","title":lang.host,"style":{"width":"250px"}}, - {"name":"source","title":lang.source,"breakpoints":"xs sm"}, - {"name":"keep_spam","title":lang.spamfilter, "type": "text","style":{"maxWidth":"80px","width":"80px"}}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/fwdhost/all', - jsonp: false, - error: function () { - console.log('Cannot draw forwarding hosts table'); - }, - success: function (data) { - return process_table_data(data, 'forwardinghoststable'); - } - }), - "empty": lang.empty, - "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, - "sorting": {"enabled": true} - }); - } - function draw_relayhosts() { - ft_relayhoststable = FooTable.init('#relayhoststable', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, - {"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, - {"name":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}}, - {"name":"username","title":lang.username,"breakpoints":"xs sm"}, - {"name":"used_by_domains","title":lang.in_use_by,"style":{"width":"110px"}, "type": "text","breakpoints":"xs sm"}, - {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"280px","width":"280px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/relayhost/all', - jsonp: false, - error: function () { - console.log('Cannot draw forwarding hosts table'); - }, - success: function (data) { - return process_table_data(data, 'relayhoststable'); - } - }), - "empty": lang.empty, - "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, - "sorting": {"enabled": true} - }); - } - - function process_table_data(data, table) { - if (table == 'relayhoststable') { - $.each(data, function (i, item) { - item.action = '<div class="btn-group">' + - '<a href="#" data-toggle="modal" id="miau" data-target="#testRelayhostModal" data-relayhost-id="' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-stats"></span> Test</a>' + - '<a href="/edit.php?relayhost=' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-rlshost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />'; - }); - } else if (table == 'forwardinghoststable') { - $.each(data, function (i, item) { - item.action = '<div class="btn-group">' + - '<a href="#" id="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - if (item.keep_spam == "yes") { - item.keep_spam = lang.no; - } - else { - item.keep_spam = lang.yes; - } - item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />'; - }); - } else if (table == 'domainadminstable') { - $.each(data, function (i, item) { - item.selected_domains = escapeHtml(item.selected_domains.toString().replace(/,/g, " ")); - item.chkbox = '<input type="checkbox" data-id="domain_admins" name="multi_select" value="' + item.username + '" />'; - item.action = '<div class="btn-group">' + - '<a href="/edit.php?domainadmin=' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-success"><span class="glyphicon glyphicon-user"></span> Login</a>' + - '</div>'; - }); - } - return data - }; - // Initial table drawings - draw_domain_admins(); - draw_fwd_hosts(); - draw_relayhosts(); - // Relayhost - $('#testRelayhostModal').on('show.bs.modal', function (e) { - $('#test_relayhost_result').text("-"); - button = $(e.relatedTarget) - if (button != null) { - $('#relayhost_id').val(button.data('relayhost-id')); - } - }) - $('#test_relayhost').on('click', function (e) { - e.preventDefault(); - prev = $('#test_relayhost').text(); - $(this).prop("disabled",true); - $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> '); - $.ajax({ - type: 'GET', - url: 'inc/ajax/relay_check.php', - dataType: 'text', - data: $('#test_relayhost_form').serialize(), - complete: function (data) { - $('#test_relayhost_result').html(data.responseText); - $('#test_relayhost').prop("disabled",false); - $('#test_relayhost').text(prev); - } - }); - }) - // DKIM private key modal - $('#showDKIMprivKey').on('show.bs.modal', function (e) { - $('#priv_key_pre').text("-"); - p_related = $(e.relatedTarget) - if (p_related != null) { - var decoded_key = Base64.decode((p_related.data('priv-key'))); - $('#priv_key_pre').text(decoded_key); - } - }) - // App links - function add_table_row(table_id) { - var row = $('<tr />'); - cols = '<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required></td>'; - cols += '<td><input class="input-sm form-control" data-id="app_links" type="text" name="href" required></td>'; - cols += '<td><a href="#" role="button" class="btn btn-xs btn-default" type="button">Remove row</a></td>'; - row.append(cols); - table_id.append(row); - } - $('#app_link_table').on('click', 'tr a', function (e) { - e.preventDefault(); - $(this).parents('tr').remove(); - }); - $('#add_app_link_row').click(function() { - add_table_row($('#app_link_table')); - }); -}); -$(window).load(function(){ - initial_width = $("#sidebar-admin").width(); - $("#scrollbox").css("width", initial_width); - if (sessionStorage.scrollTop > 70) { - $('#scrollbox').addClass('scrollboxFixed'); - } - $(window).bind('scroll', function() { - if ($(window).scrollTop() > 70) { - $('#scrollbox').addClass('scrollboxFixed'); - } else { - $('#scrollbox').removeClass('scrollboxFixed'); - } - }); -}); -function resizeScrollbox() { - on_resize_width = $("#sidebar-admin").width(); - $("#scrollbox").removeAttr("style"); - $("#scrollbox").css("width", on_resize_width); -} -$(window).on('resize', resizeScrollbox); -$('a[data-toggle="tab"]').on('shown.bs.tab', resizeScrollbox); diff --git a/data/web/js/bootstrap-slider.min.js b/data/web/js/bootstrap-slider.min.js deleted file mode 100644 index 72363a27..00000000 --- a/data/web/js/bootstrap-slider.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! ======================================================= - VERSION 9.7.2 -========================================================= */ -"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f<e.length;f++){var h=e[f],i=c[h];i="undefined"!=typeof i?i:d(this.element,h),i=null!==i?i:this.defaultOptions[h],this.options||(this.options={}),this.options[h]=i}"auto"===this.options.rtl&&(this.options.rtl="rtl"===window.getComputedStyle(this.element).direction),"vertical"!==this.options.orientation||"top"!==this.options.tooltip_position&&"bottom"!==this.options.tooltip_position?"horizontal"!==this.options.orientation||"left"!==this.options.tooltip_position&&"right"!==this.options.tooltip_position||(this.options.tooltip_position="top"):this.options.rtl?this.options.tooltip_position="left":this.options.tooltip_position="right";var j,k,l,m,n,o=this.element.style.width,p=!1,q=this.element.parentNode;if(this.sliderElem)p=!0;else{this.sliderElem=document.createElement("div"),this.sliderElem.className="slider";var r=document.createElement("div");if(r.className="slider-track",k=document.createElement("div"),k.className="slider-track-low",j=document.createElement("div"),j.className="slider-selection",l=document.createElement("div"),l.className="slider-track-high",m=document.createElement("div"),m.className="slider-handle min-slider-handle",m.setAttribute("role","slider"),m.setAttribute("aria-valuemin",this.options.min),m.setAttribute("aria-valuemax",this.options.max),n=document.createElement("div"),n.className="slider-handle max-slider-handle",n.setAttribute("role","slider"),n.setAttribute("aria-valuemin",this.options.min),n.setAttribute("aria-valuemax",this.options.max),r.appendChild(k),r.appendChild(j),r.appendChild(l),this.rangeHighlightElements=[],Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var s=0;s<this.options.rangeHighlights.length;s++){var t=document.createElement("div");t.className="slider-rangeHighlight slider-selection",this.rangeHighlightElements.push(t),r.appendChild(t)}var u=Array.isArray(this.options.labelledby);if(u&&this.options.labelledby[0]&&m.setAttribute("aria-labelledby",this.options.labelledby[0]),u&&this.options.labelledby[1]&&n.setAttribute("aria-labelledby",this.options.labelledby[1]),!u&&this.options.labelledby&&(m.setAttribute("aria-labelledby",this.options.labelledby),n.setAttribute("aria-labelledby",this.options.labelledby)),this.ticks=[],Array.isArray(this.options.ticks)&&this.options.ticks.length>0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f<this.options.ticks.length;f++){var v=document.createElement("div");if(v.className="slider-tick",this.options.ticks_tooltip){var w=this._addTickListener(),x=w.addMouseEnter(this,v,f),y=w.addMouseLeave(this,v);this.ticksCallbackMap[f]={mouseEnter:x,mouseLeave:y}}this.ticks.push(v),this.ticksContainer.appendChild(v)}j.className+=" tick-slider-selection"}if(this.tickLabels=[],Array.isArray(this.options.ticks_labels)&&this.options.ticks_labels.length>0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",f=0;f<this.options.ticks_labels.length;f++){var z=document.createElement("div"),A=0===this.options.ticks_positions.length,B=this.options.reversed&&A?this.options.ticks_labels.length-(f+1):f;z.className="slider-tick-label",z.innerHTML=this.options.ticks_labels[B],this.tickLabels.push(z),this.tickLabelContainer.appendChild(z)}var C=function(a){var b=document.createElement("div");b.className="tooltip-arrow";var c=document.createElement("div");c.className="tooltip-inner",a.appendChild(b),a.appendChild(c)},D=document.createElement("div");D.className="tooltip tooltip-main",D.setAttribute("role","presentation"),C(D);var E=document.createElement("div");E.className="tooltip tooltip-min",E.setAttribute("role","presentation"),C(E);var F=document.createElement("div");F.className="tooltip tooltip-max",F.setAttribute("role","presentation"),C(F),this.sliderElem.appendChild(r),this.sliderElem.appendChild(D),this.sliderElem.appendChild(E),this.sliderElem.appendChild(F),this.tickLabelContainer&&this.sliderElem.appendChild(this.tickLabelContainer),this.ticksContainer&&this.sliderElem.appendChild(this.ticksContainer),this.sliderElem.appendChild(m),this.sliderElem.appendChild(n),q.insertBefore(this.sliderElem,this.element),this.element.style.display="none"}if(a&&(this.$element=a(this.element),this.$sliderElem=a(this.sliderElem)),this.eventToCallbackMap={},this.sliderElem.id=this.options.id,this.touchCapable="ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,this.touchX=0,this.touchY=0,this.tooltip=this.sliderElem.querySelector(".tooltip-main"),this.tooltipInner=this.tooltip.querySelector(".tooltip-inner"),this.tooltip_min=this.sliderElem.querySelector(".tooltip-min"),this.tooltipInner_min=this.tooltip_min.querySelector(".tooltip-inner"),this.tooltip_max=this.sliderElem.querySelector(".tooltip-max"),this.tooltipInner_max=this.tooltip_max.querySelector(".tooltip-inner"),g[this.options.scale]&&(this.options.scale=g[this.options.scale]),p===!0&&(this._removeClass(this.sliderElem,"slider-horizontal"),this._removeClass(this.sliderElem,"slider-vertical"),this._removeClass(this.sliderElem,"slider-rtl"),this._removeClass(this.tooltip,"hide"),this._removeClass(this.tooltip_min,"hide"),this._removeClass(this.tooltip_max,"hide"),["left","right","top","width","height"].forEach(function(a){this._removeProperty(this.trackLow,a),this._removeProperty(this.trackSelection,a),this._removeProperty(this.trackHigh,a)},this),[this.handle1,this.handle2].forEach(function(a){this._removeProperty(a,"left"),this._removeProperty(a,"right"),this._removeProperty(a,"top")},this),[this.tooltip,this.tooltip_min,this.tooltip_max].forEach(function(a){this._removeProperty(a,"left"),this._removeProperty(a,"right"),this._removeProperty(a,"top"),this._removeProperty(a,"margin-left"),this._removeProperty(a,"margin-right"),this._removeProperty(a,"margin-top"),this._removeClass(a,"right"),this._removeClass(a,"left"),this._removeClass(a,"top")},this)),"vertical"===this.options.orientation?(this._addClass(this.sliderElem,"slider-vertical"),this.stylePos="top",this.mousePos="pageY",this.sizePos="offsetHeight"):(this._addClass(this.sliderElem,"slider-horizontal"),this.sliderElem.style.width=o,this.options.orientation="horizontal",this.options.rtl?this.stylePos="right":this.stylePos="left",this.mousePos="pageX",this.sizePos="offsetWidth"),this.options.rtl&&this._addClass(this.sliderElem,"slider-rtl"),this._setTooltipPosition(),Array.isArray(this.options.ticks)&&this.options.ticks.length>0&&(this.options.max=Math.max.apply(Math,this.options.ticks),this.options.min=Math.min.apply(Math,this.options.ticks)),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),f=0;f<this.ticks.length;f++)this._removeClass(this.ticks[f],"round triangle hide");var G=["round","triangle","custom"],H=-1!==G.indexOf(this.options.handle);if(H)for(this._addClass(this.handle1,this.options.handle),this._addClass(this.handle2,this.options.handle),f=0;f<this.ticks.length;f++)this._addClass(this.ticks[f],this.options.handle);if(this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this.setValue(this._state.value),this.handle1Keydown=this._keydown.bind(this,0),this.handle1.addEventListener("keydown",this.handle1Keydown,!1),this.handle2Keydown=this._keydown.bind(this,1),this.handle2.addEventListener("keydown",this.handle2Keydown,!1),this.mousedown=this._mousedown.bind(this),this.touchstart=this._touchstart.bind(this),this.touchmove=this._touchmove.bind(this),this.touchCapable){var I=!1;try{var J=Object.defineProperty({},"passive",{get:function(){I=!0}});window.addEventListener("test",null,J)}catch(K){}var L=I?{passive:!0}:!1;this.sliderElem.addEventListener("touchstart",this.touchstart,L),this.sliderElem.addEventListener("touchmove",this.touchmove,L)}if(this.sliderElem.addEventListener("mousedown",this.mousedown,!1),this.resize=this._resize.bind(this),window.addEventListener("resize",this.resize,!1),"hide"===this.options.tooltip)this._addClass(this.tooltip,"hide"),this._addClass(this.tooltip_min,"hide"),this._addClass(this.tooltip_max,"hide");else if("always"===this.options.tooltip)this._showTooltip(),this._alwaysShowTooltip=!0;else{if(this.showTooltip=this._showTooltip.bind(this),this.hideTooltip=this._hideTooltip.bind(this),this.options.ticks_tooltip){var M=this._addTickListener(),N=M.addMouseEnter(this,this.handle1),O=M.addMouseLeave(this,this.handle1);this.handleCallbackMap.handle1={mouseEnter:N,mouseLeave:O},N=M.addMouseEnter(this,this.handle2),O=M.addMouseLeave(this,this.handle2),this.handleCallbackMap.handle2={mouseEnter:N,mouseLeave:O}}else this.sliderElem.addEventListener("mouseenter",this.showTooltip,!1),this.sliderElem.addEventListener("mouseleave",this.hideTooltip,!1);this.handle1.addEventListener("focus",this.showTooltip,!1),this.handle1.addEventListener("blur",this.hideTooltip,!1),this.handle2.addEventListener("focus",this.showTooltip,!1),this.handle2.addEventListener("blur",this.hideTooltip,!1)}this.options.enabled?this.enable():this.disable()}var f={formatInvalidInputErrorMsg:function(a){return"Invalid input value '"+a+"' passed in"},callingContextNotSliderInstance:"Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method"},g={linear:{toValue:function(a){var b=a/100*(this.options.max-this.options.min),c=!0;if(this.options.ticks_positions.length>0){for(var d,e,f,g=0,h=1;h<this.options.ticks_positions.length;h++)if(a<=this.options.ticks_positions[h]){d=this.options.ticks[h-1],f=this.options.ticks_positions[h-1],e=this.options.ticks[h],g=this.options.ticks_positions[h];break}var i=(a-f)/(g-f);b=d+i*(e-d),c=!1}var j=c?this.options.min:0,k=j+Math.round(b/this.options.step)*this.options.step;return k<this.options.min?this.options.min:k>this.options.max?this.options.max:k},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f<this.options.ticks.length;f++)if(a<=this.options.ticks[f]){b=f>0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,d<this.options.min?this.options.min:d>this.options.max?this.options.max:d},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];return this._setDataVal(f),b===!0&&this._trigger("slide",f),d!==f&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),this.$element.removeData("slider"))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(){return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._resize(),this._layout(),this},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b<a.length;b++)a[b].removeEventListener("mouseenter",this.ticksCallbackMap[b].mouseEnter,!1),a[b].removeEventListener("mouseleave",this.ticksCallbackMap[b].mouseLeave,!1);this.handle1.removeEventListener("mouseenter",this.handleCallbackMap.handle1.mouseEnter,!1),this.handle2.removeEventListener("mouseenter",this.handleCallbackMap.handle2.mouseEnter,!1),this.handle1.removeEventListener("mouseleave",this.handleCallbackMap.handle1.mouseLeave,!1),this.handle2.removeEventListener("mouseleave",this.handleCallbackMap.handle2.mouseLeave,!1)}this.handleCallbackMap=null,this.ticksCallbackMap=null,this.showTooltip&&(this.handle1.removeEventListener("focus",this.showTooltip,!1),this.handle2.removeEventListener("focus",this.showTooltip,!1)),this.hideTooltip&&(this.handle1.removeEventListener("blur",this.hideTooltip,!1),this.handle2.removeEventListener("blur",this.hideTooltip,!1)),this.showTooltip&&this.sliderElem.removeEventListener("mouseenter",this.showTooltip,!1),this.hideTooltip&&this.sliderElem.removeEventListener("mouseleave",this.hideTooltip,!1),this.sliderElem.removeEventListener("touchstart",this.touchstart,!1),this.sliderElem.removeEventListener("touchmove",this.touchmove,!1),this.sliderElem.removeEventListener("mousedown",this.mousedown,!1),window.removeEventListener("resize",this.resize,!1)},_bindNonQueryEventHandler:function(a,b){void 0===this.eventToCallbackMap[a]&&(this.eventToCallbackMap[a]=[]),this.eventToCallbackMap[a].push(b)},_unbindNonQueryEventHandler:function(a,b){var c=this.eventToCallbackMap[a];if(void 0!==c)for(var d=0;d<c.length;d++)if(c[d]===b){c.splice(d,1);break}},_cleanUpEventCallbacksMap:function(){for(var a=Object.keys(this.eventToCallbackMap),b=0;b<a.length;b++){var c=a[b];delete this.eventToCallbackMap[c]}},_showTooltip:function(){this.options.tooltip_split===!1?(this._addClass(this.tooltip,"in"),this.tooltip_min.style.display="none",this.tooltip_max.style.display="none"):(this._addClass(this.tooltip_min,"in"),this._addClass(this.tooltip_max,"in"),this.tooltip.style.display="none"),this._state.over=!0},_hideTooltip:function(){this._state.inDrag===!1&&this.alwaysShowTooltip!==!0&&(this._removeClass(this.tooltip,"in"),this._removeClass(this.tooltip_min,"in"),this._removeClass(this.tooltip_max,"in")),this._state.over=!1},_setToolTipOnMouseOver:function(a){function b(a,b){return b?[100-a.percentage[0],this.options.range?100-a.percentage[1]:a.percentage[1]]:[a.percentage[0],a.percentage[1]]}var c=this.options.formatter(a?a.value[0]:this._state.value[0]),d=a?b(a,this.options.reversed):b(this._state,this.options.reversed);this._setText(this.tooltipInner,c),this.tooltip.style[this.stylePos]=d[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px")},_addTickListener:function(){return{addMouseEnter:function(a,b,c){var d=function(){var b=a._state,d=c>=0?c:this.attributes["aria-valuenow"].value,e=parseInt(d,10);b.value[0]=e,b.percentage[0]=a.options.ticks_positions[e],a._setToolTipOnMouseOver(b),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),isNaN(this.options.formatter(this._state.value[0]))&&this.handle1.setAttribute("aria-valuetext",this.options.formatter(this._state.value[0])),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),isNaN(this.options.formatter(this._state.value[1]))&&this.handle2.setAttribute("aria-valuetext",this.options.formatter(this._state.value[1])),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var b=0;b<this.options.rangeHighlights.length;b++){var c=this._toPercentage(this.options.rangeHighlights[b].start),d=this._toPercentage(this.options.rangeHighlights[b].end);if(this.options.reversed){var e=100-d;d=100-c,c=e}var f=this._createHighlightRange(c,d);f?"vertical"===this.options.orientation?(this.rangeHighlightElements[b].style.top=f.start+"%",this.rangeHighlightElements[b].style.height=f.size+"%"):(this.options.rtl?this.rangeHighlightElements[b].style.right=f.start+"%":this.rangeHighlightElements[b].style.left=f.start+"%",this.rangeHighlightElements[b].style.width=f.size+"%"):this.rangeHighlightElements[b].style.display="none"}if(Array.isArray(this.options.ticks)&&this.options.ticks.length>0){var g,h="vertical"===this.options.orientation?"height":"width";g="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var i=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var j=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[g]=-i/2+"px"),j=this.tickLabelContainer.offsetHeight;else for(k=0;k<this.tickLabelContainer.childNodes.length;k++)this.tickLabelContainer.childNodes[k].offsetHeight>j&&(j=this.tickLabelContainer.childNodes[k].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=j+"px")}for(var k=0;k<this.options.ticks.length;k++){var l=this.options.ticks_positions[k]||this._toPercentage(this.options.ticks[k]);this.options.reversed&&(l=100-l),this.ticks[k].style[this.stylePos]=l+"%",this._removeClass(this.ticks[k],"in-selection"),this.options.range?l>=a[0]&&l<=a[1]&&this._addClass(this.ticks[k],"in-selection"):"after"===this.options.selection&&l>=a[0]?this._addClass(this.ticks[k],"in-selection"):"before"===this.options.selection&&l<=a[0]&&this._addClass(this.ticks[k],"in-selection"),this.tickLabels[k]&&(this.tickLabels[k].style[h]=i+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[k]?(this.tickLabels[k].style.position="absolute",this.tickLabels[k].style[this.stylePos]=l+"%",this.tickLabels[k].style[g]=-i/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[k].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[k].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[g]=this.sliderElem.offsetWidth/2*-1+"px"))}}var m;if(this.options.range){m=this.options.formatter(this._state.value),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px");var n=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,n);var o=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,o),this.tooltip_min.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip_min,"margin-"+this.stylePos,-this.tooltip_min.offsetHeight/2+"px"):this._css(this.tooltip_min,"margin-"+this.stylePos,-this.tooltip_min.offsetWidth/2+"px"),this.tooltip_max.style[this.stylePos]=a[1]+"%","vertical"===this.options.orientation?this._css(this.tooltip_max,"margin-"+this.stylePos,-this.tooltip_max.offsetHeight/2+"px"):this._css(this.tooltip_max,"margin-"+this.stylePos,-this.tooltip_max.offsetWidth/2+"px")}else m=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px");if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var p=this.tooltip_min.getBoundingClientRect(),q=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?p.right>q.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):p.right>q.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this._layout(),this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this._setDataVal(e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){if(void 0===a.changedTouches)return void this._mousedown(a);var b=a.changedTouches[0];this.touchX=b.pageX,this.touchY=b.pageY},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this._state.value[a]+c*this.options.step,g=f/this.options.max*100;if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(g);var h=this._state.keyCtrl?this._state.value[0]:f,i=this._state.keyCtrl?f:this._state.value[1];f=[h,i]}return this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._setDataVal(f),this._trigger("slideStop",f),this._layout(),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this._layout();var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){if(void 0!==a.changedTouches){var b=a.changedTouches[0],c=b.pageX-this.touchX,d=b.pageY-this.touchY;this._state.inDrag||("vertical"===this.options.orientation&&5>=c&&c>=-5&&(d>=15||-15>=d)?this._mousedown(a):5>=d&&d>=-5&&(c>=15||-15>=c)&&this._mousedown(a))}},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)<c?(this._state.percentage[0]=this._state.percentage[1],this._state.dragged=1):1===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[0],b)>c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._state.value[1]/this.options.max*100<a?(this._state.percentage[0]=this._state.percentage[1],this._state.keyCtrl=1,this.handle2.focus()):1===this._state.keyCtrl&&this._state.value[0]/this.options.max*100>a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(){if(!this._state.enabled)return!1;this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var a=this._calculateValue(!0);return this._layout(),this._setDataVal(a),this._trigger("slideStop",a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this._state.percentage[0]&&(b[0]=this._toValue(this._state.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this._state.percentage[1]&&(b[1]=this._toValue(this._state.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this._state.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;d<this.options.ticks.length;d++){ -var e=Math.abs(this.options.ticks[d]-b);e<=c[1]&&(c=[this.options.ticks[d],e])}if(c[1]<=this.options.ticks_snap_bounds)return c[0]}return b},_applyPrecision:function(a){var b=this.options.precision||this._getNumDigitsAfterDecimalPlace(this.options.step);return this._applyToFixedAndParseFloat(a,b)},_getNumDigitsAfterDecimalPlace:function(a){var b=(""+a).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return b?Math.max(0,(b[1]?b[1].length:0)-(b[2]?+b[2]:0)):0},_applyToFixedAndParseFloat:function(a,b){var c=a.toFixed(b);return parseFloat(c)},_getPercentage:function(a){!this.touchCapable||"touchstart"!==a.type&&"touchmove"!==a.type||(a=a.touches[0]);var b=a[this.mousePos],c=this._state.offset[this.stylePos],d=b-c;"right"===this.stylePos&&(d=-d);var e=d/this._state.size*100;return e=Math.round(e/this._state.percentage[2])*this._state.percentage[2],this.options.reversed&&(e=100-e),Math.max(0,Math.min(100,e))},_validateInputValue:function(a){if(isNaN(+a)){if(Array.isArray(a))return this._validateArray(a),a;throw new Error(f.formatInvalidInputErrorMsg(a))}return+a},_validateArray:function(a){for(var b=0;b<a.length;b++){var c=a[b];if("number"!=typeof c)throw new Error(f.formatInvalidInputErrorMsg(c))}},_setDataVal:function(a){this.element.setAttribute("data-value",a),this.element.setAttribute("value",a),this.element.value=a},_trigger:function(b,c){c=c||0===c?c:void 0;var d=this.eventToCallbackMap[b];if(d&&d.length)for(var e=0;e<d.length;e++){var f=d[e];f(c)}a&&this._triggerJQueryEvent(b,c)},_triggerJQueryEvent:function(a,b){var c={type:a,value:b};this.$element.trigger(c),this.$sliderElem.trigger(c)},_unbindJQueryEventHandlers:function(){this.$element.off(),this.$sliderElem.off()},_setText:function(a,b){"undefined"!=typeof a.textContent?a.textContent=b:"undefined"!=typeof a.innerText&&(a.innerText=b)},_removeClass:function(a,b){for(var c=b.split(" "),d=a.className,e=0;e<c.length;e++){var f=c[e],g=new RegExp("(?:\\s|^)"+f+"(?:\\s|$)");d=d.replace(g," ")}a.className=d.trim()},_addClass:function(a,b){for(var c=b.split(" "),d=a.className,e=0;e<c.length;e++){var f=c[e],g=new RegExp("(?:\\s|^)"+f+"(?:\\s|$)"),h=g.test(d);h||(d+=" "+f)}a.className=d.trim()},_offsetLeft:function(a){return a.getBoundingClientRect().left},_offsetRight:function(a){return a.getBoundingClientRect().right},_offsetTop:function(a){for(var b=a.offsetTop;(a=a.offsetParent)&&!isNaN(a.offsetTop);)b+=a.offsetTop,"BODY"!==a.tagName&&(b-=a.scrollTop);return b},_offset:function(a){return{left:this._offsetLeft(a),right:this._offsetRight(a),top:this._offsetTop(a)}},_css:function(b,c,d){if(a)a.style(b,c,d);else{var e=c.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(a,b){return b.toUpperCase()});b.style[e]=d}},_toValue:function(a){return this.options.scale.toValue.apply(this,[a])},_toPercentage:function(a){return this.options.scale.toPercentage.apply(this,[a])},_setTooltipPosition:function(){var a=[this.tooltip,this.tooltip_min,this.tooltip_max];if("vertical"===this.options.orientation){var b;b=this.options.tooltip_position?this.options.tooltip_position:this.options.rtl?"left":"right";var c="left"===b?"right":"left";a.forEach(function(a){this._addClass(a,b),a.style[c]="100%"}.bind(this))}else"bottom"===this.options.tooltip_position?a.forEach(function(a){this._addClass(a,"bottom"),a.style.top="22px"}.bind(this)):a.forEach(function(a){this._addClass(a,"top"),a.style.top=-this.tooltip.outerHeight-14+"px"}.bind(this))}},a&&a.fn&&!function(){var e=void 0;a.fn.slider?(windowIsDefined&&window.console.warn("bootstrap-slider.js - WARNING: $.fn.slider namespace is already bound. Use the $.fn.bootstrapSlider namespace instead."),e=c):(a.bridget(b,d),e=b),a.bridget(c,d),a(function(){a("input[data-provide=slider]")[e]()})}()}(a),d}); \ No newline at end of file diff --git a/data/web/js/jquery-1.12.4.min.js b/data/web/js/build/000-jquery-1.12.4.min.js similarity index 100% rename from data/web/js/jquery-1.12.4.min.js rename to data/web/js/build/000-jquery-1.12.4.min.js diff --git a/data/web/js/bootstrap.min.js b/data/web/js/build/001-bootstrap.min.js similarity index 100% rename from data/web/js/bootstrap.min.js rename to data/web/js/build/001-bootstrap.min.js diff --git a/data/web/js/bootstrap-switch.min.js b/data/web/js/build/002-bootstrap-switch.min.js similarity index 100% rename from data/web/js/bootstrap-switch.min.js rename to data/web/js/build/002-bootstrap-switch.min.js diff --git a/data/web/js/build/003-bootstrap-slider.min.js b/data/web/js/build/003-bootstrap-slider.min.js new file mode 100644 index 00000000..633988c8 --- /dev/null +++ b/data/web/js/build/003-bootstrap-slider.min.js @@ -0,0 +1,5 @@ +/*! ======================================================= + VERSION 10.6.1 +========================================================= */ +"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1,tickIndex:null},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=c.hasOwnProperty("min"),g=c.hasOwnProperty("max"),i=0;i<e.length;i++){var j=e[i],k=c[j];k="undefined"!=typeof k?k:d(this.element,j),k=null!==k?k:this.defaultOptions[j],this.options||(this.options={}),this.options[j]=k}if(this.ticksAreValid=Array.isArray(this.options.ticks)&&this.options.ticks.length>0,this.ticksAreValid||(this.options.lock_to_ticks=!1),"auto"===this.options.rtl){var l=window.getComputedStyle(this.element);null!=l?this.options.rtl="rtl"===l.direction:this.options.rtl="rtl"===this.element.style.direction}"vertical"!==this.options.orientation||"top"!==this.options.tooltip_position&&"bottom"!==this.options.tooltip_position?"horizontal"!==this.options.orientation||"left"!==this.options.tooltip_position&&"right"!==this.options.tooltip_position||(this.options.tooltip_position="top"):this.options.rtl?this.options.tooltip_position="left":this.options.tooltip_position="right";var m,n,o,p,q,r=this.element.style.width,s=!1,t=this.element.parentNode;if(this.sliderElem)s=!0;else{this.sliderElem=document.createElement("div"),this.sliderElem.className="slider";var u=document.createElement("div");u.className="slider-track",n=document.createElement("div"),n.className="slider-track-low",m=document.createElement("div"),m.className="slider-selection",o=document.createElement("div"),o.className="slider-track-high",p=document.createElement("div"),p.className="slider-handle min-slider-handle",p.setAttribute("role","slider"),p.setAttribute("aria-valuemin",this.options.min),p.setAttribute("aria-valuemax",this.options.max),q=document.createElement("div"),q.className="slider-handle max-slider-handle",q.setAttribute("role","slider"),q.setAttribute("aria-valuemin",this.options.min),q.setAttribute("aria-valuemax",this.options.max),u.appendChild(n),u.appendChild(m),u.appendChild(o),this.rangeHighlightElements=[];var v=this.options.rangeHighlights;if(Array.isArray(v)&&v.length>0)for(var w=0;w<v.length;w++){var x=document.createElement("div"),y=v[w]["class"]||"";x.className="slider-rangeHighlight slider-selection "+y,this.rangeHighlightElements.push(x),u.appendChild(x)}var z=Array.isArray(this.options.labelledby);if(z&&this.options.labelledby[0]&&p.setAttribute("aria-labelledby",this.options.labelledby[0]),z&&this.options.labelledby[1]&&q.setAttribute("aria-labelledby",this.options.labelledby[1]),!z&&this.options.labelledby&&(p.setAttribute("aria-labelledby",this.options.labelledby),q.setAttribute("aria-labelledby",this.options.labelledby)),this.ticks=[],Array.isArray(this.options.ticks)&&this.options.ticks.length>0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",i=0;i<this.options.ticks.length;i++){var A=document.createElement("div");if(A.className="slider-tick",this.options.ticks_tooltip){var B=this._addTickListener(),C=B.addMouseEnter(this,A,i),D=B.addMouseLeave(this,A);this.ticksCallbackMap[i]={mouseEnter:C,mouseLeave:D}}this.ticks.push(A),this.ticksContainer.appendChild(A)}m.className+=" tick-slider-selection"}if(this.tickLabels=[],Array.isArray(this.options.ticks_labels)&&this.options.ticks_labels.length>0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",i=0;i<this.options.ticks_labels.length;i++){var E=document.createElement("div"),F=0===this.options.ticks_positions.length,G=this.options.reversed&&F?this.options.ticks_labels.length-(i+1):i;E.className="slider-tick-label",E.innerHTML=this.options.ticks_labels[G],this.tickLabels.push(E),this.tickLabelContainer.appendChild(E)}var H=function(a){var b=document.createElement("div");b.className="tooltip-arrow";var c=document.createElement("div");c.className="tooltip-inner",a.appendChild(b),a.appendChild(c)},I=document.createElement("div");I.className="tooltip tooltip-main",I.setAttribute("role","presentation"),H(I);var J=document.createElement("div");J.className="tooltip tooltip-min",J.setAttribute("role","presentation"),H(J);var K=document.createElement("div");K.className="tooltip tooltip-max",K.setAttribute("role","presentation"),H(K),this.sliderElem.appendChild(u),this.sliderElem.appendChild(I),this.sliderElem.appendChild(J),this.sliderElem.appendChild(K),this.tickLabelContainer&&this.sliderElem.appendChild(this.tickLabelContainer),this.ticksContainer&&this.sliderElem.appendChild(this.ticksContainer),this.sliderElem.appendChild(p),this.sliderElem.appendChild(q),t.insertBefore(this.sliderElem,this.element),this.element.style.display="none"}if(a&&(this.$element=a(this.element),this.$sliderElem=a(this.sliderElem)),this.eventToCallbackMap={},this.sliderElem.id=this.options.id,this.touchCapable="ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,this.touchX=0,this.touchY=0,this.tooltip=this.sliderElem.querySelector(".tooltip-main"),this.tooltipInner=this.tooltip.querySelector(".tooltip-inner"),this.tooltip_min=this.sliderElem.querySelector(".tooltip-min"),this.tooltipInner_min=this.tooltip_min.querySelector(".tooltip-inner"),this.tooltip_max=this.sliderElem.querySelector(".tooltip-max"),this.tooltipInner_max=this.tooltip_max.querySelector(".tooltip-inner"),h[this.options.scale]&&(this.options.scale=h[this.options.scale]),s===!0&&(this._removeClass(this.sliderElem,"slider-horizontal"),this._removeClass(this.sliderElem,"slider-vertical"),this._removeClass(this.sliderElem,"slider-rtl"),this._removeClass(this.tooltip,"hide"),this._removeClass(this.tooltip_min,"hide"),this._removeClass(this.tooltip_max,"hide"),["left","right","top","width","height"].forEach(function(a){this._removeProperty(this.trackLow,a),this._removeProperty(this.trackSelection,a),this._removeProperty(this.trackHigh,a)},this),[this.handle1,this.handle2].forEach(function(a){this._removeProperty(a,"left"),this._removeProperty(a,"right"),this._removeProperty(a,"top")},this),[this.tooltip,this.tooltip_min,this.tooltip_max].forEach(function(a){this._removeProperty(a,"left"),this._removeProperty(a,"right"),this._removeProperty(a,"top"),this._removeClass(a,"right"),this._removeClass(a,"left"),this._removeClass(a,"top")},this)),"vertical"===this.options.orientation?(this._addClass(this.sliderElem,"slider-vertical"),this.stylePos="top",this.mousePos="pageY",this.sizePos="offsetHeight"):(this._addClass(this.sliderElem,"slider-horizontal"),this.sliderElem.style.width=r,this.options.orientation="horizontal",this.options.rtl?this.stylePos="right":this.stylePos="left",this.mousePos="clientX",this.sizePos="offsetWidth"),this.options.rtl&&this._addClass(this.sliderElem,"slider-rtl"),this._setTooltipPosition(),Array.isArray(this.options.ticks)&&this.options.ticks.length>0&&(g||(this.options.max=Math.max.apply(Math,this.options.ticks)),f||(this.options.min=Math.min.apply(Math,this.options.ticks))),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=n||this.trackLow,this.trackSelection=m||this.trackSelection,this.trackHigh=o||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=p||this.handle1,this.handle2=q||this.handle2,s===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),i=0;i<this.ticks.length;i++)this._removeClass(this.ticks[i],"round triangle hide");var L=["round","triangle","custom"],M=-1!==L.indexOf(this.options.handle);if(M)for(this._addClass(this.handle1,this.options.handle),this._addClass(this.handle2,this.options.handle),i=0;i<this.ticks.length;i++)this._addClass(this.ticks[i],this.options.handle);if(this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this.setValue(this._state.value),this.handle1Keydown=this._keydown.bind(this,0),this.handle1.addEventListener("keydown",this.handle1Keydown,!1),this.handle2Keydown=this._keydown.bind(this,1),this.handle2.addEventListener("keydown",this.handle2Keydown,!1),this.mousedown=this._mousedown.bind(this),this.touchstart=this._touchstart.bind(this),this.touchmove=this._touchmove.bind(this),this.touchCapable&&(this.sliderElem.addEventListener("touchstart",this.touchstart,!1),this.sliderElem.addEventListener("touchmove",this.touchmove,!1)),this.sliderElem.addEventListener("mousedown",this.mousedown,!1),this.resize=this._resize.bind(this),window.addEventListener("resize",this.resize,!1),"hide"===this.options.tooltip)this._addClass(this.tooltip,"hide"),this._addClass(this.tooltip_min,"hide"),this._addClass(this.tooltip_max,"hide");else if("always"===this.options.tooltip)this._showTooltip(),this._alwaysShowTooltip=!0;else{if(this.showTooltip=this._showTooltip.bind(this),this.hideTooltip=this._hideTooltip.bind(this),this.options.ticks_tooltip){var N=this._addTickListener(),O=N.addMouseEnter(this,this.handle1),P=N.addMouseLeave(this,this.handle1);this.handleCallbackMap.handle1={mouseEnter:O,mouseLeave:P},O=N.addMouseEnter(this,this.handle2),P=N.addMouseLeave(this,this.handle2),this.handleCallbackMap.handle2={mouseEnter:O,mouseLeave:P}}else this.sliderElem.addEventListener("mouseenter",this.showTooltip,!1),this.sliderElem.addEventListener("mouseleave",this.hideTooltip,!1),this.touchCapable&&(this.sliderElem.addEventListener("touchstart",this.showTooltip,!1),this.sliderElem.addEventListener("touchmove",this.showTooltip,!1),this.sliderElem.addEventListener("touchend",this.hideTooltip,!1));this.handle1.addEventListener("focus",this.showTooltip,!1),this.handle1.addEventListener("blur",this.hideTooltip,!1),this.handle2.addEventListener("focus",this.showTooltip,!1),this.handle2.addEventListener("blur",this.hideTooltip,!1),this.touchCapable&&(this.handle1.addEventListener("touchstart",this.showTooltip,!1),this.handle1.addEventListener("touchmove",this.showTooltip,!1),this.handle1.addEventListener("touchend",this.hideTooltip,!1),this.handle2.addEventListener("touchstart",this.showTooltip,!1),this.handle2.addEventListener("touchmove",this.showTooltip,!1),this.handle2.addEventListener("touchend",this.hideTooltip,!1))}this.options.enabled?this.enable():this.disable()}var f=void 0,g={formatInvalidInputErrorMsg:function(a){return"Invalid input value '"+a+"' passed in"},callingContextNotSliderInstance:"Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method"},h={linear:{getValue:function(a,b){return a<b.min?b.min:a>b.max?b.max:a},toValue:function(a){var b=a/100*(this.options.max-this.options.min),c=!0;if(this.options.ticks_positions.length>0){for(var d,e,f,g=0,i=1;i<this.options.ticks_positions.length;i++)if(a<=this.options.ticks_positions[i]){d=this.options.ticks[i-1],f=this.options.ticks_positions[i-1],e=this.options.ticks[i],g=this.options.ticks_positions[i];break}var j=(a-f)/(g-f);b=d+j*(e-d),c=!1}var k=c?this.options.min:0,l=k+Math.round(b/this.options.step)*this.options.step;return h.linear.getValue(l,this.options)},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f<this.options.ticks.length;f++)if(a<=this.options.ticks[f]){b=f>0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=1-this.options.min,c=Math.log(this.options.min+b),d=Math.log(this.options.max+b),e=Math.exp(c+(d-c)*a/100)-b;return Math.round(e)===d?d:(e=this.options.min+Math.round((e-this.options.min)/this.options.step)*this.options.step,h.linear.getValue(e,this.options))},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=1-this.options.min,c=Math.log(this.options.max+b),d=Math.log(this.options.min+b),e=Math.log(a+b);return 100*(e-d)/(c-d)}}};d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,lock_to_ticks:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this.ticksAreValid&&this.options.lock_to_ticks&&(this._state.value[0]=this.options.ticks[this._getClosestTickIndex(this._state.value[0])],this._state.value[1]=this.options.ticks[this._getClosestTickIndex(this._state.value[1])]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this.ticksAreValid&&this.options.lock_to_ticks&&(this._state.value=this.options.ticks[this._getClosestTickIndex(this._state.value)]),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this._setTickIndex(),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];this._setDataVal(f),b===!0&&this._trigger("slide",f);var g=!1;return g=Array.isArray(f)?d[0]!==f[0]||d[1]!==f[1]:d!==f,g&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),f===b&&this.$element.removeData(f),this.$element.removeData(c))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(d){var g=this.getValue();return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),d&&d.useCurrentValue===!0&&this.setValue(g),a&&(f===b?(a.data(this.element,b,this),a.data(this.element,c,this)):a.data(this.element,c,this)),this},relayout:function(){return this._resize(),this},_removeTooltipListener:function(a,b){this.handle1.removeEventListener(a,b,!1),this.handle2.removeEventListener(a,b,!1)},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b<a.length;b++)a[b].removeEventListener("mouseenter",this.ticksCallbackMap[b].mouseEnter,!1),a[b].removeEventListener("mouseleave",this.ticksCallbackMap[b].mouseLeave,!1);this.handleCallbackMap.handle1&&this.handleCallbackMap.handle2&&(this.handle1.removeEventListener("mouseenter",this.handleCallbackMap.handle1.mouseEnter,!1),this.handle2.removeEventListener("mouseenter",this.handleCallbackMap.handle2.mouseEnter,!1),this.handle1.removeEventListener("mouseleave",this.handleCallbackMap.handle1.mouseLeave,!1),this.handle2.removeEventListener("mouseleave",this.handleCallbackMap.handle2.mouseLeave,!1))}this.handleCallbackMap=null,this.ticksCallbackMap=null,this.showTooltip&&this._removeTooltipListener("focus",this.showTooltip),this.hideTooltip&&this._removeTooltipListener("blur",this.hideTooltip),this.showTooltip&&this.sliderElem.removeEventListener("mouseenter",this.showTooltip,!1),this.hideTooltip&&this.sliderElem.removeEventListener("mouseleave",this.hideTooltip,!1),this.sliderElem.removeEventListener("mousedown",this.mousedown,!1),this.touchCapable&&(this.showTooltip&&(this.handle1.removeEventListener("touchstart",this.showTooltip,!1),this.handle1.removeEventListener("touchmove",this.showTooltip,!1),this.handle2.removeEventListener("touchstart",this.showTooltip,!1),this.handle2.removeEventListener("touchmove",this.showTooltip,!1)),this.hideTooltip&&(this.handle1.removeEventListener("touchend",this.hideTooltip,!1),this.handle2.removeEventListener("touchend",this.hideTooltip,!1)),this.showTooltip&&(this.sliderElem.removeEventListener("touchstart",this.showTooltip,!1),this.sliderElem.removeEventListener("touchmove",this.showTooltip,!1)),this.hideTooltip&&this.sliderElem.removeEventListener("touchend",this.hideTooltip,!1),this.sliderElem.removeEventListener("touchstart",this.touchstart,!1),this.sliderElem.removeEventListener("touchmove",this.touchmove,!1)),window.removeEventListener("resize",this.resize,!1)},_bindNonQueryEventHandler:function(a,b){void 0===this.eventToCallbackMap[a]&&(this.eventToCallbackMap[a]=[]),this.eventToCallbackMap[a].push(b)},_unbindNonQueryEventHandler:function(a,b){var c=this.eventToCallbackMap[a];if(void 0!==c)for(var d=0;d<c.length;d++)if(c[d]===b){c.splice(d,1);break}},_cleanUpEventCallbacksMap:function(){for(var a=Object.keys(this.eventToCallbackMap),b=0;b<a.length;b++){var c=a[b];delete this.eventToCallbackMap[c]}},_showTooltip:function(){this.options.tooltip_split===!1?(this._addClass(this.tooltip,"in"),this.tooltip_min.style.display="none",this.tooltip_max.style.display="none"):(this._addClass(this.tooltip_min,"in"),this._addClass(this.tooltip_max,"in"),this.tooltip.style.display="none"),this._state.over=!0},_hideTooltip:function(){this._state.inDrag===!1&&this._alwaysShowTooltip!==!0&&(this._removeClass(this.tooltip,"in"),this._removeClass(this.tooltip_min,"in"),this._removeClass(this.tooltip_max,"in")),this._state.over=!1},_setToolTipOnMouseOver:function(a){function b(a,b){return b?[100-a.percentage[0],c.options.range?100-a.percentage[1]:a.percentage[1]]:[a.percentage[0],a.percentage[1]]}var c=this,d=this.options.formatter(a?a.value[0]:this._state.value[0]),e=a?b(a,this.options.reversed):b(this._state,this.options.reversed);this._setText(this.tooltipInner,d),this.tooltip.style[this.stylePos]=e[0]+"%"},_copyState:function(){return{value:[this._state.value[0],this._state.value[1]],enabled:this._state.enabled,offset:this._state.offset,size:this._state.size,percentage:[this._state.percentage[0],this._state.percentage[1],this._state.percentage[2]],inDrag:this._state.inDrag,over:this._state.over,dragged:this._state.dragged,keyCtrl:this._state.keyCtrl}},_addTickListener:function(){return{addMouseEnter:function(a,b,c){var d=function(){var d=a._copyState(),e=b===a.handle1?d.value[0]:d.value[1],f=void 0;void 0!==c?(e=a.options.ticks[c],f=a.options.ticks_positions.length>0&&a.options.ticks_positions[c]||a._toPercentage(a.options.ticks[c])):f=a._toPercentage(e),d.value[0]=e,d.percentage[0]=f,a._setToolTipOnMouseOver(d),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a,b;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),b=this.options.formatter(this._state.value[0]),isNaN(b)?this.handle1.setAttribute("aria-valuetext",b):this.handle1.removeAttribute("aria-valuetext"),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),b=this.options.formatter(this._state.value[1]),isNaN(b)?this.handle2.setAttribute("aria-valuetext",b):this.handle2.removeAttribute("aria-valuetext"),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var c=0;c<this.options.rangeHighlights.length;c++){var d=this._toPercentage(this.options.rangeHighlights[c].start),e=this._toPercentage(this.options.rangeHighlights[c].end);if(this.options.reversed){var f=100-e;e=100-d,d=f}var g=this._createHighlightRange(d,e);g?"vertical"===this.options.orientation?(this.rangeHighlightElements[c].style.top=g.start+"%",this.rangeHighlightElements[c].style.height=g.size+"%"):(this.options.rtl?this.rangeHighlightElements[c].style.right=g.start+"%":this.rangeHighlightElements[c].style.left=g.start+"%",this.rangeHighlightElements[c].style.width=g.size+"%"):this.rangeHighlightElements[c].style.display="none"}if(Array.isArray(this.options.ticks)&&this.options.ticks.length>0){var h,i="vertical"===this.options.orientation?"height":"width";h="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var j=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var k=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[h]=-j/2+"px"),k=this.tickLabelContainer.offsetHeight;else for(l=0;l<this.tickLabelContainer.childNodes.length;l++)this.tickLabelContainer.childNodes[l].offsetHeight>k&&(k=this.tickLabelContainer.childNodes[l].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=k+"px")}for(var l=0;l<this.options.ticks.length;l++){var m=this.options.ticks_positions[l]||this._toPercentage(this.options.ticks[l]);this.options.reversed&&(m=100-m),this.ticks[l].style[this.stylePos]=m+"%",this._removeClass(this.ticks[l],"in-selection"),this.options.range?m>=a[0]&&m<=a[1]&&this._addClass(this.ticks[l],"in-selection"):"after"===this.options.selection&&m>=a[0]?this._addClass(this.ticks[l],"in-selection"):"before"===this.options.selection&&m<=a[0]&&this._addClass(this.ticks[l],"in-selection"),this.tickLabels[l]&&(this.tickLabels[l].style[i]=j+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[l]?(this.tickLabels[l].style.position="absolute",this.tickLabels[l].style[this.stylePos]=m+"%",this.tickLabels[l].style[h]=-j/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[l].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[l].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[h]=this.sliderElem.offsetWidth/2*-1+"px"),this._removeClass(this.tickLabels[l],"label-in-selection label-is-selection"),this.options.range?m>=a[0]&&m<=a[1]&&(this._addClass(this.tickLabels[l],"label-in-selection"),(m===a[0]||a[1])&&this._addClass(this.tickLabels[l],"label-is-selection")):("after"===this.options.selection&&m>=a[0]?this._addClass(this.tickLabels[l],"label-in-selection"):"before"===this.options.selection&&m<=a[0]&&this._addClass(this.tickLabels[l],"label-in-selection"),m===a[0]&&this._addClass(this.tickLabels[l],"label-is-selection")))}}var n;if(this.options.range){n=this.options.formatter(this._state.value),this._setText(this.tooltipInner,n),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%";var o=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,o);var p=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,p),this.tooltip_min.style[this.stylePos]=a[0]+"%",this.tooltip_max.style[this.stylePos]=a[1]+"%"}else n=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,n),this.tooltip.style[this.stylePos]=a[0]+"%";if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var q=this.tooltip_min.getBoundingClientRect(),r=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?q.right>r.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):q.right>r.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;a.preventDefault&&a.preventDefault(),this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){this._mousedown(a)},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="horizontal"===this.options.orientation,e="vertical"===this.options.orientation,f=this.options.rtl,g=this.options.reversed;d?f?g||(c=-c):g&&(c=-c):e&&(g||(c=-c))}var h;if(this.ticksAreValid&&this.options.lock_to_ticks){var i=void 0;i=this.options.ticks.indexOf(this._state.value[a]),-1===i&&(i=0,window.console.warn("(lock_to_ticks) _keydown: index should not be -1")),i+=c,i=Math.max(0,Math.min(this.options.ticks.length-1,i)),h=this.options.ticks[i]}else h=this._state.value[a]+c*this.options.step;var j=this._toPercentage(h);if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(j);var k=this._state.keyCtrl?this._state.value[0]:h,l=this._state.keyCtrl?h:this._state.value[1];h=[Math.max(this.options.min,Math.min(this.options.max,k)),Math.max(this.options.min,Math.min(this.options.max,l))]}else h=Math.max(this.options.min,Math.min(this.options.max,h));return this._trigger("slideStart",h),this.setValue(h,!0,!0),this._trigger("slideStop",h),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1; +var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b;var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){void 0!==a.changedTouches&&a.preventDefault&&a.preventDefault()},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)<c?(this._state.percentage[0]=this._state.percentage[1],this._state.dragged=1):1===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[0],b)>c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._toPercentage(this._state.value[1])<a?(this._state.percentage[0]=this._state.percentage[1],this._state.keyCtrl=1,this.handle2.focus()):1===this._state.keyCtrl&&this._toPercentage(this._state.value[0])>a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var c=this._calculateValue(!0);return this.setValue(c,!1,!0),this._trigger("slideStop",c),this._state.dragged=null,!1},_setValues:function(a,b){var c=0===a?0:100;this._state.percentage[a]!==c&&(b.data[a]=this._toValue(this._state.percentage[a]),b.data[a]=this._applyPrecision(b.data[a]))},_calculateValue:function(a){var b={};return this.options.range?(b.data=[this.options.min,this.options.max],this._setValues(0,b),this._setValues(1,b),a&&(b.data[0]=this._snapToClosestTick(b.data[0]),b.data[1]=this._snapToClosestTick(b.data[1]))):(b.data=this._toValue(this._state.percentage[0]),b.data=parseFloat(b.data),b.data=this._applyPrecision(b.data),a&&(b.data=this._snapToClosestTick(b.data))),b.data},_snapToClosestTick:function(a){for(var b=[a,1/0],c=0;c<this.options.ticks.length;c++){var d=Math.abs(this.options.ticks[c]-a);d<=b[1]&&(b=[this.options.ticks[c],d])}return b[1]<=this.options.ticks_snap_bounds?b[0]:a},_applyPrecision:function(a){var b=this.options.precision||this._getNumDigitsAfterDecimalPlace(this.options.step);return this._applyToFixedAndParseFloat(a,b)},_getNumDigitsAfterDecimalPlace:function(a){var b=(""+a).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return b?Math.max(0,(b[1]?b[1].length:0)-(b[2]?+b[2]:0)):0},_applyToFixedAndParseFloat:function(a,b){var c=a.toFixed(b);return parseFloat(c)},_getPercentage:function(a){!this.touchCapable||"touchstart"!==a.type&&"touchmove"!==a.type&&"touchend"!==a.type||(a=a.changedTouches[0]);var b=a[this.mousePos],c=this._state.offset[this.stylePos],d=b-c;"right"===this.stylePos&&(d=-d);var e=d/this._state.size*100;return e=Math.round(e/this._state.percentage[2])*this._state.percentage[2],this.options.reversed&&(e=100-e),Math.max(0,Math.min(100,e))},_validateInputValue:function(a){if(isNaN(+a)){if(Array.isArray(a))return this._validateArray(a),a;throw new Error(g.formatInvalidInputErrorMsg(a))}return+a},_validateArray:function(a){for(var b=0;b<a.length;b++){var c=a[b];if("number"!=typeof c)throw new Error(g.formatInvalidInputErrorMsg(c))}},_setDataVal:function(a){this.element.setAttribute("data-value",a),this.element.setAttribute("value",a),this.element.value=a},_trigger:function(b,c){c=c||0===c?c:void 0;var d=this.eventToCallbackMap[b];if(d&&d.length)for(var e=0;e<d.length;e++){var f=d[e];f(c)}a&&this._triggerJQueryEvent(b,c)},_triggerJQueryEvent:function(a,b){var c={type:a,value:b};this.$element.trigger(c),this.$sliderElem.trigger(c)},_unbindJQueryEventHandlers:function(){this.$element.off(),this.$sliderElem.off()},_setText:function(a,b){"undefined"!=typeof a.textContent?a.textContent=b:"undefined"!=typeof a.innerText&&(a.innerText=b)},_removeClass:function(a,b){for(var c=b.split(" "),d=a.className,e=0;e<c.length;e++){var f=c[e],g=new RegExp("(?:\\s|^)"+f+"(?:\\s|$)");d=d.replace(g," ")}a.className=d.trim()},_addClass:function(a,b){for(var c=b.split(" "),d=a.className,e=0;e<c.length;e++){var f=c[e],g=new RegExp("(?:\\s|^)"+f+"(?:\\s|$)"),h=g.test(d);h||(d+=" "+f)}a.className=d.trim()},_offsetLeft:function(a){return a.getBoundingClientRect().left},_offsetRight:function(a){return a.getBoundingClientRect().right},_offsetTop:function(a){for(var b=a.offsetTop;(a=a.offsetParent)&&!isNaN(a.offsetTop);)b+=a.offsetTop,"BODY"!==a.tagName&&(b-=a.scrollTop);return b},_offset:function(a){return{left:this._offsetLeft(a),right:this._offsetRight(a),top:this._offsetTop(a)}},_css:function(b,c,d){if(a)a.style(b,c,d);else{var e=c.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(a,b){return b.toUpperCase()});b.style[e]=d}},_toValue:function(a){return this.options.scale.toValue.apply(this,[a])},_toPercentage:function(a){return this.options.scale.toPercentage.apply(this,[a])},_setTooltipPosition:function(){var a=[this.tooltip,this.tooltip_min,this.tooltip_max];if("vertical"===this.options.orientation){var b;b=this.options.tooltip_position?this.options.tooltip_position:this.options.rtl?"left":"right";var c="left"===b?"right":"left";a.forEach(function(a){this._addClass(a,b),a.style[c]="100%"}.bind(this))}else"bottom"===this.options.tooltip_position?a.forEach(function(a){this._addClass(a,"bottom"),a.style.top="22px"}.bind(this)):a.forEach(function(a){this._addClass(a,"top"),a.style.top=-this.tooltip.outerHeight-14+"px"}.bind(this))},_getClosestTickIndex:function(a){for(var b=Math.abs(a-this.options.ticks[0]),c=0,d=0;d<this.options.ticks.length;++d){var e=Math.abs(a-this.options.ticks[d]);b>e&&(b=e,c=d)}return c},_setTickIndex:function(){this.ticksAreValid&&(this._state.tickIndex=[this.options.ticks.indexOf(this._state.value[0]),this.options.ticks.indexOf(this._state.value[1])])}},a&&a.fn&&(a.fn.slider?(windowIsDefined&&window.console.warn("bootstrap-slider.js - WARNING: $.fn.slider namespace is already bound. Use the $.fn.bootstrapSlider namespace instead."),f=c):(a.bridget(b,d),f=b),a.bridget(c,d),a(function(){a("input[data-provide=slider]")[f]()}))}(a),d}); \ No newline at end of file diff --git a/data/web/js/bootstrap-select.min.js b/data/web/js/build/004-bootstrap-select.min.js similarity index 99% rename from data/web/js/bootstrap-select.min.js rename to data/web/js/build/004-bootstrap-select.min.js index 9e20a22e..14572226 100644 --- a/data/web/js/bootstrap-select.min.js +++ b/data/web/js/build/004-bootstrap-select.min.js @@ -5,4 +5,4 @@ * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) */ !function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function(a){!function(a){"use strict";function b(b){var c=[{re:/[\xC0-\xC6]/g,ch:"A"},{re:/[\xE0-\xE6]/g,ch:"a"},{re:/[\xC8-\xCB]/g,ch:"E"},{re:/[\xE8-\xEB]/g,ch:"e"},{re:/[\xCC-\xCF]/g,ch:"I"},{re:/[\xEC-\xEF]/g,ch:"i"},{re:/[\xD2-\xD6]/g,ch:"O"},{re:/[\xF2-\xF6]/g,ch:"o"},{re:/[\xD9-\xDC]/g,ch:"U"},{re:/[\xF9-\xFC]/g,ch:"u"},{re:/[\xC7-\xE7]/g,ch:"c"},{re:/[\xD1]/g,ch:"N"},{re:/[\xF1]/g,ch:"n"}];return a.each(c,function(){b=b?b.replace(this.re,this.ch):""}),b}function c(b){var c=arguments,d=b;[].shift.apply(c);var e,f=this.each(function(){var b=a(this);if(b.is("select")){var f=b.data("selectpicker"),g="object"==typeof d&&d;if(f){if(g)for(var h in g)g.hasOwnProperty(h)&&(f.options[h]=g[h])}else{var i=a.extend({},k.DEFAULTS,a.fn.selectpicker.defaults||{},b.data(),g);i.template=a.extend({},k.DEFAULTS.template,a.fn.selectpicker.defaults?a.fn.selectpicker.defaults.template:{},b.data().template,g.template),b.data("selectpicker",f=new k(this,i))}"string"==typeof d&&(e=f[d]instanceof Function?f[d].apply(f,c):f.options[d])}});return"undefined"!=typeof e?e:f}String.prototype.includes||!function(){var a={}.toString,b=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),c="".indexOf,d=function(b){if(null==this)throw new TypeError;var d=String(this);if(b&&"[object RegExp]"==a.call(b))throw new TypeError;var e=d.length,f=String(b),g=f.length,h=arguments.length>1?arguments[1]:void 0,i=h?Number(h):0;i!=i&&(i=0);var j=Math.min(Math.max(i,0),e);return!(g+j>e)&&c.call(d,f,i)!=-1};b?b(String.prototype,"includes",{value:d,configurable:!0,writable:!0}):String.prototype.includes=d}(),String.prototype.startsWith||!function(){var a=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),b={}.toString,c=function(a){if(null==this)throw new TypeError;var c=String(this);if(a&&"[object RegExp]"==b.call(a))throw new TypeError;var d=c.length,e=String(a),f=e.length,g=arguments.length>1?arguments[1]:void 0,h=g?Number(g):0;h!=h&&(h=0);var i=Math.min(Math.max(h,0),d);if(f+i>d)return!1;for(var j=-1;++j<f;)if(c.charCodeAt(i+j)!=e.charCodeAt(j))return!1;return!0};a?a(String.prototype,"startsWith",{value:c,configurable:!0,writable:!0}):String.prototype.startsWith=c}(),Object.keys||(Object.keys=function(a,b,c){c=[];for(b in a)c.hasOwnProperty.call(a,b)&&c.push(b);return c});var d={useDefault:!1,_set:a.valHooks.select.set};a.valHooks.select.set=function(b,c){return c&&!d.useDefault&&a(b).data("selected",!0),d._set.apply(this,arguments)};var e=null;a.fn.triggerNative=function(a){var b,c=this[0];c.dispatchEvent?("function"==typeof Event?b=new Event(a,{bubbles:!0}):(b=document.createEvent("Event"),b.initEvent(a,!0,!1)),c.dispatchEvent(b)):c.fireEvent?(b=document.createEventObject(),b.eventType=a,c.fireEvent("on"+a,b)):this.trigger(a)},a.expr.pseudos.icontains=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.text()).toString().toUpperCase();return f.includes(d[3].toUpperCase())},a.expr.pseudos.ibegins=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.text()).toString().toUpperCase();return f.startsWith(d[3].toUpperCase())},a.expr.pseudos.aicontains=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.data("normalizedText")||e.text()).toString().toUpperCase();return f.includes(d[3].toUpperCase())},a.expr.pseudos.aibegins=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.data("normalizedText")||e.text()).toString().toUpperCase();return f.startsWith(d[3].toUpperCase())};var f={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},g={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},h=function(a){var b=function(b){return a[b]},c="(?:"+Object.keys(a).join("|")+")",d=RegExp(c),e=RegExp(c,"g");return function(a){return a=null==a?"":""+a,d.test(a)?a.replace(e,b):a}},i=h(f),j=h(g),k=function(b,c){d.useDefault||(a.valHooks.select.set=d._set,d.useDefault=!0),this.$element=a(b),this.$newElement=null,this.$button=null,this.$menu=null,this.$lis=null,this.options=c,null===this.options.title&&(this.options.title=this.$element.attr("title"));var e=this.options.windowPadding;"number"==typeof e&&(this.options.windowPadding=[e,e,e,e]),this.val=k.prototype.val,this.render=k.prototype.render,this.refresh=k.prototype.refresh,this.setStyle=k.prototype.setStyle,this.selectAll=k.prototype.selectAll,this.deselectAll=k.prototype.deselectAll,this.destroy=k.prototype.destroy,this.remove=k.prototype.remove,this.show=k.prototype.show,this.hide=k.prototype.hide,this.init()};k.VERSION="1.12.2",k.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(a,b){return 1==a?"{0} item selected":"{0} items selected"},maxOptionsText:function(a,b){return[1==a?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==b?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:"btn-default",size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:"glyphicon",tickIcon:"glyphicon-ok",showTick:!1,template:{caret:'<span class="caret"></span>'},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0},k.prototype={constructor:k,init:function(){var b=this,c=this.$element.attr("id");this.$element.addClass("bs-select-hidden"),this.liObj={},this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$newElement=this.createView(),this.$element.after(this.$newElement).appendTo(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(".dropdown-menu"),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.$element.removeClass("bs-select-hidden"),this.options.dropdownAlignRight===!0&&this.$menu.addClass("dropdown-menu-right"),"undefined"!=typeof c&&(this.$button.attr("data-id",c),a('label[for="'+c+'"]').click(function(a){a.preventDefault(),b.$button.focus()})),this.checkDisabled(),this.clickListener(),this.options.liveSearch&&this.liveSearchListener(),this.render(),this.setStyle(),this.setWidth(),this.options.container&&this.selectPosition(),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(a){b.$menuInner.attr("aria-expanded",!1),b.$element.trigger("hide.bs.select",a)},"hidden.bs.dropdown":function(a){b.$element.trigger("hidden.bs.select",a)},"show.bs.dropdown":function(a){b.$menuInner.attr("aria-expanded",!0),b.$element.trigger("show.bs.select",a)},"shown.bs.dropdown":function(a){b.$element.trigger("shown.bs.select",a)}}),b.$element[0].hasAttribute("required")&&this.$element.on("invalid",function(){b.$button.addClass("bs-invalid").focus(),b.$element.on({"focus.bs.select":function(){b.$button.focus(),b.$element.off("focus.bs.select")},"shown.bs.select":function(){b.$element.val(b.$element.val()).off("shown.bs.select")},"rendered.bs.select":function(){this.validity.valid&&b.$button.removeClass("bs-invalid"),b.$element.off("rendered.bs.select")}})}),setTimeout(function(){b.$element.trigger("loaded.bs.select")})},createDropdown:function(){var b=this.multiple||this.options.showTick?" show-tick":"",c=this.$element.parent().hasClass("input-group")?" input-group-btn":"",d=this.autofocus?" autofocus":"",e=this.options.header?'<div class="popover-title"><button type="button" class="close" aria-hidden="true">×</button>'+this.options.header+"</div>":"",f=this.options.liveSearch?'<div class="bs-searchbox"><input type="text" class="form-control" autocomplete="off"'+(null===this.options.liveSearchPlaceholder?"":' placeholder="'+i(this.options.liveSearchPlaceholder)+'"')+' role="textbox" aria-label="Search"></div>':"",g=this.multiple&&this.options.actionsBox?'<div class="bs-actionsbox"><div class="btn-group btn-group-sm btn-block"><button type="button" class="actions-btn bs-select-all btn btn-default">'+this.options.selectAllText+'</button><button type="button" class="actions-btn bs-deselect-all btn btn-default">'+this.options.deselectAllText+"</button></div></div>":"",h=this.multiple&&this.options.doneButton?'<div class="bs-donebutton"><div class="btn-group btn-block"><button type="button" class="btn btn-sm btn-default">'+this.options.doneButtonText+"</button></div></div>":"",j='<div class="btn-group bootstrap-select'+b+c+'"><button type="button" class="'+this.options.styleBase+' dropdown-toggle" data-toggle="dropdown"'+d+' role="button"><span class="filter-option pull-left"></span> <span class="bs-caret">'+this.options.template.caret+'</span></button><div class="dropdown-menu open" role="combobox">'+e+f+g+'<ul class="dropdown-menu inner" role="listbox" aria-expanded="false"></ul>'+h+"</div></div>";return a(j)},createView:function(){var a=this.createDropdown(),b=this.createLi();return a.find("ul")[0].innerHTML=b,a},reloadLi:function(){var a=this.createLi();this.$menuInner[0].innerHTML=a},createLi:function(){var c=this,d=[],e=0,f=document.createElement("option"),g=-1,h=function(a,b,c,d){return"<li"+("undefined"!=typeof c&""!==c?' class="'+c+'"':"")+("undefined"!=typeof b&null!==b?' data-original-index="'+b+'"':"")+("undefined"!=typeof d&null!==d?'data-optgroup="'+d+'"':"")+">"+a+"</li>"},j=function(d,e,f,g){return'<a tabindex="0"'+("undefined"!=typeof e?' class="'+e+'"':"")+(f?' style="'+f+'"':"")+(c.options.liveSearchNormalize?' data-normalized-text="'+b(i(a(d).html()))+'"':"")+("undefined"!=typeof g||null!==g?' data-tokens="'+g+'"':"")+' role="option">'+d+'<span class="'+c.options.iconBase+" "+c.options.tickIcon+' check-mark"></span></a>'};if(this.options.title&&!this.multiple&&(g--,!this.$element.find(".bs-title-option").length)){var k=this.$element[0];f.className="bs-title-option",f.innerHTML=this.options.title,f.value="",k.insertBefore(f,k.firstChild);var l=a(k.options[k.selectedIndex]);void 0===l.attr("selected")&&void 0===this.$element.data("selected")&&(f.selected=!0)}return this.$element.find("option").each(function(b){var f=a(this);if(g++,!f.hasClass("bs-title-option")){var k=this.className||"",l=this.style.cssText,m=f.data("content")?f.data("content"):f.html(),n=f.data("tokens")?f.data("tokens"):null,o="undefined"!=typeof f.data("subtext")?'<small class="text-muted">'+f.data("subtext")+"</small>":"",p="undefined"!=typeof f.data("icon")?'<span class="'+c.options.iconBase+" "+f.data("icon")+'"></span> ':"",q=f.parent(),r="OPTGROUP"===q[0].tagName,s=r&&q[0].disabled,t=this.disabled||s;if(""!==p&&t&&(p="<span>"+p+"</span>"),c.options.hideDisabled&&(t&&!r||s))return void g--;if(f.data("content")||(m=p+'<span class="text">'+m+o+"</span>"),r&&f.data("divider")!==!0){if(c.options.hideDisabled&&t){if(void 0===q.data("allOptionsDisabled")){var u=q.children();q.data("allOptionsDisabled",u.filter(":disabled").length===u.length)}if(q.data("allOptionsDisabled"))return void g--}var v=" "+q[0].className||"";if(0===f.index()){e+=1;var w=q[0].label,x="undefined"!=typeof q.data("subtext")?'<small class="text-muted">'+q.data("subtext")+"</small>":"",y=q.data("icon")?'<span class="'+c.options.iconBase+" "+q.data("icon")+'"></span> ':"";w=y+'<span class="text">'+i(w)+x+"</span>",0!==b&&d.length>0&&(g++,d.push(h("",null,"divider",e+"div"))),g++,d.push(h(w,null,"dropdown-header"+v,e))}if(c.options.hideDisabled&&t)return void g--;d.push(h(j(m,"opt "+k+v,l,n),b,"",e))}else if(f.data("divider")===!0)d.push(h("",b,"divider"));else if(f.data("hidden")===!0)d.push(h(j(m,k,l,n),b,"hidden is-hidden"));else{var z=this.previousElementSibling&&"OPTGROUP"===this.previousElementSibling.tagName;if(!z&&c.options.hideDisabled)for(var A=a(this).prevAll(),B=0;B<A.length;B++)if("OPTGROUP"===A[B].tagName){for(var C=0,D=0;D<B;D++){var E=A[D];(E.disabled||a(E).data("hidden")===!0)&&C++}C===B&&(z=!0);break}z&&(g++,d.push(h("",null,"divider",e+"div"))),d.push(h(j(m,k,l,n),b))}c.liObj[b]=g}}),this.multiple||0!==this.$element.find("option:selected").length||this.options.title||this.$element.find("option").eq(0).prop("selected",!0).attr("selected","selected"),d.join("")},findLis:function(){return null==this.$lis&&(this.$lis=this.$menu.find("li")),this.$lis},render:function(b){var c,d=this;b!==!1&&this.$element.find("option").each(function(a){var b=d.findLis().eq(d.liObj[a]);d.setDisabled(a,this.disabled||"OPTGROUP"===this.parentNode.tagName&&this.parentNode.disabled,b),d.setSelected(a,this.selected,b)}),this.togglePlaceholder(),this.tabIndex();var e=this.$element.find("option").map(function(){if(this.selected){if(d.options.hideDisabled&&(this.disabled||"OPTGROUP"===this.parentNode.tagName&&this.parentNode.disabled))return;var b,c=a(this),e=c.data("icon")&&d.options.showIcon?'<i class="'+d.options.iconBase+" "+c.data("icon")+'"></i> ':"";return b=d.options.showSubtext&&c.data("subtext")&&!d.multiple?' <small class="text-muted">'+c.data("subtext")+"</small>":"","undefined"!=typeof c.attr("title")?c.attr("title"):c.data("content")&&d.options.showContent?c.data("content").toString():e+c.html()+b}}).toArray(),f=this.multiple?e.join(this.options.multipleSeparator):e[0];if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var g=this.options.selectedTextFormat.split(">");if(g.length>1&&e.length>g[1]||1==g.length&&e.length>=2){c=this.options.hideDisabled?", [disabled]":"";var h=this.$element.find("option").not('[data-divider="true"], [data-hidden="true"]'+c).length,i="function"==typeof this.options.countSelectedText?this.options.countSelectedText(e.length,h):this.options.countSelectedText;f=i.replace("{0}",e.length.toString()).replace("{1}",h.toString())}}void 0==this.options.title&&(this.options.title=this.$element.attr("title")),"static"==this.options.selectedTextFormat&&(f=this.options.title),f||(f="undefined"!=typeof this.options.title?this.options.title:this.options.noneSelectedText),this.$button.attr("title",j(a.trim(f.replace(/<[^>]*>?/g,"")))),this.$button.children(".filter-option").html(f),this.$element.trigger("rendered.bs.select")},setStyle:function(a,b){this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,""));var c=a?a:this.options.style;"add"==b?this.$button.addClass(c):"remove"==b?this.$button.removeClass(c):(this.$button.removeClass(this.options.style),this.$button.addClass(c))},liHeight:function(b){if(b||this.options.size!==!1&&!this.sizeInfo){var c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),f=document.createElement("li"),g=document.createElement("li"),h=document.createElement("a"),i=document.createElement("span"),j=this.options.header&&this.$menu.find(".popover-title").length>0?this.$menu.find(".popover-title")[0].cloneNode(!0):null,k=this.options.liveSearch?document.createElement("div"):null,l=this.options.actionsBox&&this.multiple&&this.$menu.find(".bs-actionsbox").length>0?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,m=this.options.doneButton&&this.multiple&&this.$menu.find(".bs-donebutton").length>0?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null;if(i.className="text",c.className=this.$menu[0].parentNode.className+" open",d.className="dropdown-menu open",e.className="dropdown-menu inner",f.className="divider",i.appendChild(document.createTextNode("Inner text")),h.appendChild(i),g.appendChild(h),e.appendChild(g),e.appendChild(f),j&&d.appendChild(j),k){var n=document.createElement("input");k.className="bs-searchbox",n.className="form-control",k.appendChild(n),d.appendChild(k)}l&&d.appendChild(l),d.appendChild(e),m&&d.appendChild(m),c.appendChild(d),document.body.appendChild(c);var o=h.offsetHeight,p=j?j.offsetHeight:0,q=k?k.offsetHeight:0,r=l?l.offsetHeight:0,s=m?m.offsetHeight:0,t=a(f).outerHeight(!0),u="function"==typeof getComputedStyle&&getComputedStyle(d),v=u?null:a(d),w={vert:parseInt(u?u.paddingTop:v.css("paddingTop"))+parseInt(u?u.paddingBottom:v.css("paddingBottom"))+parseInt(u?u.borderTopWidth:v.css("borderTopWidth"))+parseInt(u?u.borderBottomWidth:v.css("borderBottomWidth")),horiz:parseInt(u?u.paddingLeft:v.css("paddingLeft"))+parseInt(u?u.paddingRight:v.css("paddingRight"))+parseInt(u?u.borderLeftWidth:v.css("borderLeftWidth"))+parseInt(u?u.borderRightWidth:v.css("borderRightWidth"))},x={vert:w.vert+parseInt(u?u.marginTop:v.css("marginTop"))+parseInt(u?u.marginBottom:v.css("marginBottom"))+2,horiz:w.horiz+parseInt(u?u.marginLeft:v.css("marginLeft"))+parseInt(u?u.marginRight:v.css("marginRight"))+2};document.body.removeChild(c),this.sizeInfo={liHeight:o,headerHeight:p,searchHeight:q,actionsHeight:r,doneButtonHeight:s,dividerHeight:t,menuPadding:w,menuExtras:x}}},setSize:function(){if(this.findLis(),this.liHeight(),this.options.header&&this.$menu.css("padding-top",0),this.options.size!==!1){var b,c,d,e,f,g,h,i,j=this,k=this.$menu,l=this.$menuInner,m=a(window),n=this.$newElement[0].offsetHeight,o=this.$newElement[0].offsetWidth,p=this.sizeInfo.liHeight,q=this.sizeInfo.headerHeight,r=this.sizeInfo.searchHeight,s=this.sizeInfo.actionsHeight,t=this.sizeInfo.doneButtonHeight,u=this.sizeInfo.dividerHeight,v=this.sizeInfo.menuPadding,w=this.sizeInfo.menuExtras,x=this.options.hideDisabled?".disabled":"",y=function(){var b,c=j.$newElement.offset(),d=a(j.options.container);j.options.container&&!d.is("body")?(b=d.offset(),b.top+=parseInt(d.css("borderTopWidth")),b.left+=parseInt(d.css("borderLeftWidth"))):b={top:0,left:0};var e=j.options.windowPadding;f=c.top-b.top-m.scrollTop(),g=m.height()-f-n-b.top-e[2],h=c.left-b.left-m.scrollLeft(),i=m.width()-h-o-b.left-e[1],f-=e[0],h-=e[3]};if(y(),"auto"===this.options.size){var z=function(){var m,n=function(b,c){return function(d){return c?d.classList?d.classList.contains(b):a(d).hasClass(b):!(d.classList?d.classList.contains(b):a(d).hasClass(b))}},u=j.$menuInner[0].getElementsByTagName("li"),x=Array.prototype.filter?Array.prototype.filter.call(u,n("hidden",!1)):j.$lis.not(".hidden"),z=Array.prototype.filter?Array.prototype.filter.call(x,n("dropdown-header",!0)):x.filter(".dropdown-header");y(),b=g-w.vert,c=i-w.horiz,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height"),k.data("width")||k.data("width",k.width()),e=k.data("width")):(d=k.height(),e=k.width()),j.options.dropupAuto&&j.$newElement.toggleClass("dropup",f>g&&b-w.vert<d),j.$newElement.hasClass("dropup")&&(b=f-w.vert),"auto"===j.options.dropdownAlignRight&&k.toggleClass("dropdown-menu-right",h>i&&c-w.horiz<e-o),m=x.length+z.length>3?3*p+w.vert-2:0,k.css({"max-height":b+"px",overflow:"hidden","min-height":m+q+r+s+t+"px"}),l.css({"max-height":b-q-r-s-t-v.vert+"px","overflow-y":"auto","min-height":Math.max(m-v.vert,0)+"px"})};z(),this.$searchbox.off("input.getSize propertychange.getSize").on("input.getSize propertychange.getSize",z),m.off("resize.getSize scroll.getSize").on("resize.getSize scroll.getSize",z)}else if(this.options.size&&"auto"!=this.options.size&&this.$lis.not(x).length>this.options.size){var A=this.$lis.not(".divider").not(x).children().slice(0,this.options.size).last().parent().index(),B=this.$lis.slice(0,A+1).filter(".divider").length;b=p*this.options.size+B*u+v.vert,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height")):d=k.height(),j.options.dropupAuto&&this.$newElement.toggleClass("dropup",f>g&&b-w.vert<d),k.css({"max-height":b+q+r+s+t+"px",overflow:"hidden","min-height":""}),l.css({"max-height":b-v.vert+"px","overflow-y":"auto","min-height":""})}}},setWidth:function(){if("auto"===this.options.width){this.$menu.css("min-width","0");var a=this.$menu.parent().clone().appendTo("body"),b=this.options.container?this.$newElement.clone().appendTo("body"):a,c=a.children(".dropdown-menu").outerWidth(),d=b.css("width","auto").children("button").outerWidth();a.remove(),b.remove(),this.$newElement.css("width",Math.max(c,d)+"px")}else"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width",""));this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement.removeClass("fit-width")},selectPosition:function(){this.$bsContainer=a('<div class="bs-container" />');var b,c,d,e=this,f=a(this.options.container),g=function(a){e.$bsContainer.addClass(a.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass("dropup",a.hasClass("dropup")),b=a.offset(),f.is("body")?c={top:0,left:0}:(c=f.offset(),c.top+=parseInt(f.css("borderTopWidth"))-f.scrollTop(),c.left+=parseInt(f.css("borderLeftWidth"))-f.scrollLeft()),d=a.hasClass("dropup")?0:a[0].offsetHeight,e.$bsContainer.css({top:b.top-c.top+d,left:b.left-c.left,width:a[0].offsetWidth})};this.$button.on("click",function(){var b=a(this);e.isDisabled()||(g(e.$newElement),e.$bsContainer.appendTo(e.options.container).toggleClass("open",!b.hasClass("open")).append(e.$menu))}),a(window).on("resize scroll",function(){g(e.$newElement)}),this.$element.on("hide.bs.select",function(){e.$menu.data("height",e.$menu.height()),e.$bsContainer.detach()})},setSelected:function(a,b,c){c||(this.togglePlaceholder(),c=this.findLis().eq(this.liObj[a])),c.toggleClass("selected",b).find("a").attr("aria-selected",b)},setDisabled:function(a,b,c){c||(c=this.findLis().eq(this.liObj[a])),b?c.addClass("disabled").children("a").attr("href","#").attr("tabindex",-1).attr("aria-disabled",!0):c.removeClass("disabled").children("a").removeAttr("href").attr("tabindex",0).attr("aria-disabled",!1)},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){var a=this;this.isDisabled()?(this.$newElement.addClass("disabled"),this.$button.addClass("disabled").attr("tabindex",-1).attr("aria-disabled",!0)):(this.$button.hasClass("disabled")&&(this.$newElement.removeClass("disabled"),this.$button.removeClass("disabled").attr("aria-disabled",!1)),this.$button.attr("tabindex")!=-1||this.$element.data("tabindex")||this.$button.removeAttr("tabindex")),this.$button.click(function(){return!a.isDisabled()})},togglePlaceholder:function(){var a=this.$element.val();this.$button.toggleClass("bs-placeholder",null===a||""===a||a.constructor===Array&&0===a.length)},tabIndex:function(){this.$element.data("tabindex")!==this.$element.attr("tabindex")&&this.$element.attr("tabindex")!==-98&&"-98"!==this.$element.attr("tabindex")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex"))),this.$element.attr("tabindex",-98)},clickListener:function(){var b=this,c=a(document);c.data("spaceSelect",!1),this.$button.on("keyup",function(a){/(32)/.test(a.keyCode.toString(10))&&c.data("spaceSelect")&&(a.preventDefault(),c.data("spaceSelect",!1))}),this.$button.on("click",function(){b.setSize()}),this.$element.on("shown.bs.select",function(){if(b.options.liveSearch||b.multiple){if(!b.multiple){var a=b.liObj[b.$element[0].selectedIndex];if("number"!=typeof a||b.options.size===!1)return;var c=b.$lis.eq(a)[0].offsetTop-b.$menuInner[0].offsetTop;c=c-b.$menuInner[0].offsetHeight/2+b.sizeInfo.liHeight/2,b.$menuInner[0].scrollTop=c}}else b.$menuInner.find(".selected a").focus()}),this.$menuInner.on("click","li a",function(c){var d=a(this),f=d.parent().data("originalIndex"),g=b.$element.val(),h=b.$element.prop("selectedIndex"),i=!0;if(b.multiple&&1!==b.options.maxOptions&&c.stopPropagation(),c.preventDefault(),!b.isDisabled()&&!d.parent().hasClass("disabled")){var j=b.$element.find("option"),k=j.eq(f),l=k.prop("selected"),m=k.parent("optgroup"),n=b.options.maxOptions,o=m.data("maxOptions")||!1;if(b.multiple){if(k.prop("selected",!l),b.setSelected(f,!l),d.blur(),n!==!1||o!==!1){var p=n<j.filter(":selected").length,q=o<m.find("option:selected").length;if(n&&p||o&&q)if(n&&1==n)j.prop("selected",!1),k.prop("selected",!0),b.$menuInner.find(".selected").removeClass("selected"),b.setSelected(f,!0);else if(o&&1==o){m.find("option:selected").prop("selected",!1),k.prop("selected",!0);var r=d.parent().data("optgroup");b.$menuInner.find('[data-optgroup="'+r+'"]').removeClass("selected"),b.setSelected(f,!0)}else{var s="string"==typeof b.options.maxOptionsText?[b.options.maxOptionsText,b.options.maxOptionsText]:b.options.maxOptionsText,t="function"==typeof s?s(n,o):s,u=t[0].replace("{n}",n),v=t[1].replace("{n}",o),w=a('<div class="notify"></div>');t[2]&&(u=u.replace("{var}",t[2][n>1?0:1]),v=v.replace("{var}",t[2][o>1?0:1])),k.prop("selected",!1),b.$menu.append(w),n&&p&&(w.append(a("<div>"+u+"</div>")),i=!1,b.$element.trigger("maxReached.bs.select")),o&&q&&(w.append(a("<div>"+v+"</div>")),i=!1,b.$element.trigger("maxReachedGrp.bs.select")),setTimeout(function(){b.setSelected(f,!1)},10),w.delay(750).fadeOut(300,function(){a(this).remove()})}}}else j.prop("selected",!1),k.prop("selected",!0),b.$menuInner.find(".selected").removeClass("selected").find("a").attr("aria-selected",!1),b.setSelected(f,!0);!b.multiple||b.multiple&&1===b.options.maxOptions?b.$button.focus():b.options.liveSearch&&b.$searchbox.focus(),i&&(g!=b.$element.val()&&b.multiple||h!=b.$element.prop("selectedIndex")&&!b.multiple)&&(e=[f,k.prop("selected"),l],b.$element.triggerNative("change"))}}),this.$menu.on("click","li.disabled a, .popover-title, .popover-title :not(.close)",function(c){c.currentTarget==this&&(c.preventDefault(),c.stopPropagation(),b.options.liveSearch&&!a(c.target).hasClass("close")?b.$searchbox.focus():b.$button.focus())}),this.$menuInner.on("click",".divider, .dropdown-header",function(a){a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus()}),this.$menu.on("click",".popover-title .close",function(){b.$button.click()}),this.$searchbox.on("click",function(a){a.stopPropagation()}),this.$menu.on("click",".actions-btn",function(c){b.options.liveSearch?b.$searchbox.focus():b.$button.focus(),c.preventDefault(),c.stopPropagation(),a(this).hasClass("bs-select-all")?b.selectAll():b.deselectAll()}),this.$element.change(function(){b.render(!1),b.$element.trigger("changed.bs.select",e),e=null})},liveSearchListener:function(){var c=this,d=a('<li class="no-results"></li>');this.$button.on("click.dropdown.data-api",function(){c.$menuInner.find(".active").removeClass("active"),c.$searchbox.val()&&(c.$searchbox.val(""),c.$lis.not(".is-hidden").removeClass("hidden"),d.parent().length&&d.remove()),c.multiple||c.$menuInner.find(".selected").addClass("active"),setTimeout(function(){c.$searchbox.focus()},10)}),this.$searchbox.on("click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api",function(a){a.stopPropagation()}),this.$searchbox.on("input propertychange",function(){if(c.$lis.not(".is-hidden").removeClass("hidden"),c.$lis.filter(".active").removeClass("active"),d.remove(),c.$searchbox.val()){var e,f=c.$lis.not(".is-hidden, .divider, .dropdown-header");if(e=c.options.liveSearchNormalize?f.find("a").not(":a"+c._searchStyle()+'("'+b(c.$searchbox.val())+'")'):f.find("a").not(":"+c._searchStyle()+'("'+c.$searchbox.val()+'")'),e.length===f.length)d.html(c.options.noneResultsText.replace("{0}",'"'+i(c.$searchbox.val())+'"')),c.$menuInner.append(d),c.$lis.addClass("hidden");else{e.parent().addClass("hidden");var g,h=c.$lis.not(".hidden");h.each(function(b){var c=a(this);c.hasClass("divider")?void 0===g?c.addClass("hidden"):(g&&g.addClass("hidden"),g=c):c.hasClass("dropdown-header")&&h.eq(b+1).data("optgroup")!==c.data("optgroup")?c.addClass("hidden"):g=null}),g&&g.addClass("hidden"),f.not(".hidden").first().addClass("active")}}})},_searchStyle:function(){var a={begins:"ibegins",startsWith:"ibegins"};return a[this.options.liveSearchStyle]||"icontains"},val:function(a){return"undefined"!=typeof a?(this.$element.val(a),this.render(),this.$element):this.$element.val()},changeAll:function(b){if(this.multiple){"undefined"==typeof b&&(b=!0),this.findLis();var c=this.$element.find("option"),d=this.$lis.not(".divider, .dropdown-header, .disabled, .hidden"),e=d.length,f=[];if(b){if(d.filter(".selected").length===d.length)return}else if(0===d.filter(".selected").length)return;d.toggleClass("selected",b);for(var g=0;g<e;g++){var h=d[g].getAttribute("data-original-index");f[f.length]=c.eq(h)[0]}a(f).prop("selected",b),this.render(!1),this.togglePlaceholder(),this.$element.triggerNative("change")}},selectAll:function(){return this.changeAll(!0)},deselectAll:function(){return this.changeAll(!1)},toggle:function(a){a=a||window.event,a&&a.stopPropagation(),this.$button.trigger("click")},keydown:function(c){var d,e,f,g,h,i,j,k,l,m=a(this),n=m.is("input")?m.parent().parent():m.parent(),o=n.data("this"),p=":not(.disabled, .hidden, .dropdown-header, .divider)",q={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"};if(o.options.liveSearch&&(n=m.parent().parent()),o.options.container&&(n=o.$menu),d=a('[role="listbox"] li',n),l=o.$newElement.hasClass("open"),!l&&(c.keyCode>=48&&c.keyCode<=57||c.keyCode>=96&&c.keyCode<=105||c.keyCode>=65&&c.keyCode<=90))return o.options.container?o.$button.trigger("click"):(o.setSize(),o.$menu.parent().addClass("open"),l=!0),void o.$searchbox.focus();if(o.options.liveSearch&&(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(c.preventDefault(),c.stopPropagation(),o.$menuInner.click(),o.$button.focus()),d=a('[role="listbox"] li'+p,n),m.val()||/(38|40)/.test(c.keyCode.toString(10))||0===d.filter(".active").length&&(d=o.$menuInner.find("li"),d=o.options.liveSearchNormalize?d.filter(":a"+o._searchStyle()+"("+b(q[c.keyCode])+")"):d.filter(":"+o._searchStyle()+"("+q[c.keyCode]+")"))),d.length){if(/(38|40)/.test(c.keyCode.toString(10)))e=d.index(d.find("a").filter(":focus").parent()),g=d.filter(p).first().index(),h=d.filter(p).last().index(),f=d.eq(e).nextAll(p).eq(0).index(),i=d.eq(e).prevAll(p).eq(0).index(),j=d.eq(f).prevAll(p).eq(0).index(),o.options.liveSearch&&(d.each(function(b){a(this).hasClass("disabled")||a(this).data("index",b)}),e=d.index(d.filter(".active")),g=d.first().data("index"),h=d.last().data("index"),f=d.eq(e).nextAll().eq(0).data("index"),i=d.eq(e).prevAll().eq(0).data("index"),j=d.eq(f).prevAll().eq(0).data("index")),k=m.data("prevIndex"),38==c.keyCode?(o.options.liveSearch&&e--,e!=j&&e>i&&(e=i),e<g&&(e=g),e==k&&(e=h)):40==c.keyCode&&(o.options.liveSearch&&e++,e==-1&&(e=0),e!=j&&e<f&&(e=f),e>h&&(e=h),e==k&&(e=g)),m.data("prevIndex",e),o.options.liveSearch?(c.preventDefault(),m.hasClass("dropdown-toggle")||(d.removeClass("active").eq(e).addClass("active").children("a").focus(),m.focus())):d.eq(e).children("a").focus();else if(!m.is("input")){var r,s,t=[];d.each(function(){a(this).hasClass("disabled")||a.trim(a(this).children("a").text().toLowerCase()).substring(0,1)==q[c.keyCode]&&t.push(a(this).index())}),r=a(document).data("keycount"),r++,a(document).data("keycount",r),s=a.trim(a(":focus").text().toLowerCase()).substring(0,1),s!=q[c.keyCode]?(r=1,a(document).data("keycount",r)):r>=t.length&&(a(document).data("keycount",0),r>t.length&&(r=1)),d.eq(t[r-1]).children("a").focus()}if((/(13|32)/.test(c.keyCode.toString(10))||/(^9$)/.test(c.keyCode.toString(10))&&o.options.selectOnTab)&&l){if(/(32)/.test(c.keyCode.toString(10))||c.preventDefault(),o.options.liveSearch)/(32)/.test(c.keyCode.toString(10))||(o.$menuInner.find(".active a").click(), -m.focus());else{var u=a(":focus");u.click(),u.focus(),c.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(o.multiple||o.options.liveSearch)||/(27)/.test(c.keyCode.toString(10))&&!l)&&(o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus())}},mobile:function(){this.$element.addClass("mobile-device")},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(),this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(".bs.select").removeData("selectpicker").removeClass("bs-select-hidden selectpicker")}};var l=a.fn.selectpicker;a.fn.selectpicker=c,a.fn.selectpicker.Constructor=k,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=l,this},a(document).data("keycount",0).on("keydown.bs.select",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',k.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);c.call(b,b.data())})})}(a)}); +m.focus());else{var u=a(":focus");u.click(),u.focus(),c.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(o.multiple||o.options.liveSearch)||/(27)/.test(c.keyCode.toString(10))&&!l)&&(o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus())}},mobile:function(){this.$element.addClass("mobile-device")},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(),this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(".bs.select").removeData("selectpicker").removeClass("bs-select-hidden selectpicker")}};var l=a.fn.selectpicker;a.fn.selectpicker=c,a.fn.selectpicker.Constructor=k,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=l,this},a(document).data("keycount",0).on("keydown.bs.select",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',k.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);c.call(b,b.data())})})}(a)}); \ No newline at end of file diff --git a/data/web/js/bootstrap-filestyle.min.js b/data/web/js/build/005-bootstrap-filestyle.min.js similarity index 100% rename from data/web/js/bootstrap-filestyle.min.js rename to data/web/js/build/005-bootstrap-filestyle.min.js diff --git a/data/web/js/notifications.min.js b/data/web/js/build/006-notifications.min.js similarity index 100% rename from data/web/js/notifications.min.js rename to data/web/js/build/006-notifications.min.js diff --git a/data/web/js/formcache.min.js b/data/web/js/build/007-formcache.min.js similarity index 100% rename from data/web/js/formcache.min.js rename to data/web/js/build/007-formcache.min.js diff --git a/data/web/js/google.charts.loader.js b/data/web/js/build/008-google.charts.loader.js similarity index 100% rename from data/web/js/google.charts.loader.js rename to data/web/js/build/008-google.charts.loader.js diff --git a/data/web/js/numberedtextarea.min.js b/data/web/js/build/009-numberedtextarea.min.js similarity index 100% rename from data/web/js/numberedtextarea.min.js rename to data/web/js/build/009-numberedtextarea.min.js diff --git a/data/web/js/build/010-sha1.min.js b/data/web/js/build/010-sha1.min.js new file mode 100644 index 00000000..1abe5880 --- /dev/null +++ b/data/web/js/build/010-sha1.min.js @@ -0,0 +1 @@ +!function(r){var o=function(r,o){return r<<o|r>>>32-o},e=function(r){var o,e="";for(o=7;o>=0;o--)e+=(r>>>4*o&15).toString(16);return e};jQuery.extend({sha1:function(r){var a,t,n,h,C,c,f,d,u,i=new Array(80),A=1732584193,g=4023233417,s=2562383102,S=271733878,m=3285377520,p=(r=function(r){r=r.replace(/\x0d\x0a/g,"\n");for(var o="",e=0;e<r.length;e++){var a=r.charCodeAt(e);a<128?o+=String.fromCharCode(a):a>127&&a<2048?(o+=String.fromCharCode(a>>6|192),o+=String.fromCharCode(63&a|128)):(o+=String.fromCharCode(a>>12|224),o+=String.fromCharCode(a>>6&63|128),o+=String.fromCharCode(63&a|128))}return o}(r)).length,l=new Array;for(t=0;t<p-3;t+=4)n=r.charCodeAt(t)<<24|r.charCodeAt(t+1)<<16|r.charCodeAt(t+2)<<8|r.charCodeAt(t+3),l.push(n);switch(p%4){case 0:t=2147483648;break;case 1:t=r.charCodeAt(p-1)<<24|8388608;break;case 2:t=r.charCodeAt(p-2)<<24|r.charCodeAt(p-1)<<16|32768;break;case 3:t=r.charCodeAt(p-3)<<24|r.charCodeAt(p-2)<<16|r.charCodeAt(p-1)<<8|128}for(l.push(t);l.length%16!=14;)l.push(0);for(l.push(p>>>29),l.push(p<<3&4294967295),a=0;a<l.length;a+=16){for(t=0;t<16;t++)i[t]=l[a+t];for(t=16;t<=79;t++)i[t]=o(i[t-3]^i[t-8]^i[t-14]^i[t-16],1);for(h=A,C=g,c=s,f=S,d=m,t=0;t<=19;t++)u=o(h,5)+(C&c|~C&f)+d+i[t]+1518500249&4294967295,d=f,f=c,c=o(C,30),C=h,h=u;for(t=20;t<=39;t++)u=o(h,5)+(C^c^f)+d+i[t]+1859775393&4294967295,d=f,f=c,c=o(C,30),C=h,h=u;for(t=40;t<=59;t++)u=o(h,5)+(C&c|C&f|c&f)+d+i[t]+2400959708&4294967295,d=f,f=c,c=o(C,30),C=h,h=u;for(t=60;t<=79;t++)u=o(h,5)+(C^c^f)+d+i[t]+3395469782&4294967295,d=f,f=c,c=o(C,30),C=h,h=u;A=A+h&4294967295,g=g+C&4294967295,s=s+c&4294967295,S=S+f&4294967295,m=m+d&4294967295}return(u=e(A)+e(g)+e(s)+e(S)+e(m)).toLowerCase()}})}(); \ No newline at end of file diff --git a/data/web/js/u2f-api.js b/data/web/js/build/011-u2f-api.js similarity index 99% rename from data/web/js/u2f-api.js rename to data/web/js/build/011-u2f-api.js index 37afaa0e..e8e653bf 100644 --- a/data/web/js/u2f-api.js +++ b/data/web/js/build/011-u2f-api.js @@ -20,10 +20,9 @@ * Modification: * Only continue load this library if window.u2f is not already supplied by the browser. */ - var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1; var browserImplementsU2f = !!((typeof root.u2f !== 'undefined') && root.u2f.register); - if (isFirefox && browserImplementsU2f) { + if (browserImplementsU2f) { root.u2f.isSupported = true; return; } diff --git a/data/web/js/api.js b/data/web/js/build/012-api.js similarity index 90% rename from data/web/js/api.js rename to data/web/js/build/012-api.js index 2e770f7b..2f98a840 100644 --- a/data/web/js/api.js +++ b/data/web/js/build/012-api.js @@ -5,9 +5,9 @@ $(document).ready(function() { } else { var parent_btn_grp = $(elem).parentsUntil(".btn-group").parent(); if (parent_btn_grp.hasClass('btn-group')) { - parent_btn_grp.replaceWith('<button class="btn btn-default btn-sm" disabled>' + loading_text + '</a>'); + parent_btn_grp.replaceWith('<button class="btn btn-default btn-sm" disabled>' + lang_footer.loading + '</a>'); } - $(elem).text(loading_text); + $(elem).text(lang_footer.loading); $(elem).attr('data-submitted', '1'); function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); }; $(document).on("keydown", disableF5); @@ -71,7 +71,7 @@ $(document).ready(function() { }); // General API edit actions - $(document).on('click', '#edit_selected', function(e) { + $(document).on('click', "[data-action='edit_selected']", function(e) { e.preventDefault(); var id = $(this).data('id'); var api_url = $(this).data('api-url'); @@ -81,6 +81,11 @@ $(document).ready(function() { } else { api_reload_window = true; } + if (typeof $(this).data('api-reload-location') !== 'undefined') { + api_reload_location = $(this).data('api-reload-location'); + } else { + api_reload_location = '#'; + } // If clicked element #edit_selected is in a form with the same data-id as the button, // we merge all input fields by {"name":"value"} into api-attr if ($(this).closest("form").data('id') == id) { @@ -151,7 +156,11 @@ $(document).ready(function() { response_obj = JSON.parse(response); } if (api_reload_window === true) { - window.location = window.location.href.split("#")[0]; + if (api_reload_location != '#') { + window.location.replace(api_reload_location) + } else { + window.location = window.location.href.split("#")[0]; + } } } }); @@ -159,7 +168,7 @@ $(document).ready(function() { }); // General API add actions - $(document).on('click', '#add_item', function(e) { + $(document).on('click', "[data-action='add_item']", function(e) { e.preventDefault(); var id = $(this).data('id'); var api_url = $(this).data('api-url'); @@ -184,7 +193,6 @@ $(document).ready(function() { } if ($(this).attr("max")) { if (Number($(this).val()) > Number($(this).attr("max"))) { - alert($(this).attr("max")) invalid = true; $(this).addClass('inputMissingAttr'); } else { @@ -252,7 +260,7 @@ $(document).ready(function() { }); // General API delete actions - $(document).on('click', '#delete_selected', function(e) { + $(document).on('click', "[data-action='delete_selected']", function(e) { e.preventDefault(); var id = $(this).data('id'); // If clicked element #delete_selected has data-item attribute, it is added to "items" @@ -283,6 +291,7 @@ $(document).ready(function() { keyboard: false }) .one('click', '#IsConfirmed', function(e) { + if (is_active($('#IsConfirmed'))) { return false; } $.ajax({ type: "POST", dataType: "json", @@ -304,4 +313,4 @@ $(document).ready(function() { $('#ConfirmDeleteModal').modal('hide'); }); }); -}); \ No newline at end of file +}); diff --git a/data/web/js/footable.min.js b/data/web/js/build/013-footable.min.js similarity index 100% rename from data/web/js/footable.min.js rename to data/web/js/build/013-footable.min.js diff --git a/data/web/js/build/014-mailcow.js b/data/web/js/build/014-mailcow.js new file mode 100644 index 00000000..cf3a3a72 --- /dev/null +++ b/data/web/js/build/014-mailcow.js @@ -0,0 +1,198 @@ +$(document).ready(function() { + // mailcow alert box generator + window.mailcow_alert_box = function(message, type) { + msg = $('<span/>').text(message).text(); + if (type == 'danger') { + auto_hide = 0; + $('#' + localStorage.getItem("add_modal")).modal('show'); + localStorage.removeItem("add_modal"); + } else { + auto_hide = 5000; + } + $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}}); + } + + // https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate + function shake(div,interval=100,distance=10,times=4) { + $(div).css('position','relative'); + for(var iter=0;iter<(times+1);iter++){ + $(div).animate({ left: ((iter%2==0 ? distance : distance*-1))}, interval); + } + $(div).animate({ left: 0},interval); + } + + // form cache + $('[data-cached-form="true"]').formcache({key: $(this).data('id')}); + + // tooltips + $(function () { + $('[data-toggle="tooltip"]').tooltip() + }); + + // remember last navigation pill + (function () { + 'use strict'; + if ($('a[data-toggle="tab"]').length) { + $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { + if ($(this).data('dont-remember') == 1) { + return true; + } + var id = $(this).parents('[role="tablist"]').attr('id'); + var key = 'lastTag'; + if (id) { + key += ':' + id; + } + localStorage.setItem(key, $(e.target).attr('href')); + }); + $('[role="tablist"]').each(function (idx, elem) { + var id = $(elem).attr('id'); + var key = 'lastTag'; + if (id) { + key += ':' + id; + } + var lastTab = localStorage.getItem(key); + if (lastTab) { + $('[href="' + lastTab + '"]').tab('show'); + } + }); + } + })(); + + // IE fix to hide scrollbars when table body is empty + $('tbody').filter(function (index) { + return $(this).children().length < 1; + }).remove(); + + // selectpicker + $('select').selectpicker(); + + // haveibeenpwned? + $('[data-hibp]').after('<p class="small haveibeenpwned">โช Check against haveibeenpwned.com</p><span class="hibp-out"></span>'); + $('[data-hibp]').on('input', function() { + out_field = $(this).next('.haveibeenpwned').next('.hibp-out').text('').attr('class', 'hibp-out'); + }); + $('.haveibeenpwned:not(.task-running)').on('click', function() { + var hibp_field = $(this) + $(hibp_field).addClass('task-running'); + var hibp_result = $(hibp_field).next('.hibp-out') + var password_field = $(this).prev('[data-hibp]') + if ($(password_field).val() == '') { + shake(password_field); + } + else { + $(hibp_result).attr('class', 'hibp-out label label-info'); + $(hibp_result).text(lang_footer.loading); + var password_digest = $.sha1($(password_field).val()) + var digest_five = password_digest.substring(0, 5).toUpperCase(); + var queryURL = "https://api.pwnedpasswords.com/range/" + digest_five; + var compl_digest = password_digest.substring(5, 41).toUpperCase(); + $.ajax({ + url: queryURL, + type: 'GET', + success: function(res) { + if (res.search(compl_digest) > -1){ + $(hibp_result).removeClass('label label-info').addClass('label label-danger'); + $(hibp_result).text(lang_footer.hibp_nok) + } else { + $(hibp_result).removeClass('label label-info').addClass('label label-success'); + $(hibp_result).text(lang_footer.hibp_ok) + } + $(hibp_field).removeClass('task-running'); + }, + error: function(xhr, status, error) { + $(hibp_result).removeClass('label label-info').addClass('label label-warning'); + $(hibp_result).text('API error: ' + xhr.responseText) + $(hibp_field).removeClass('task-running'); + } + }); + } + }); + + // Disable disallowed inputs + $('[data-acl="0"]').each(function(event){ + if ($(this).is("a")) { + $(this).removeAttr("data-toggle"); + $(this).removeAttr("data-target"); + } + if ($(this).hasClass('btn-group')) { + $(this).find('a').each(function(){ + $(this).removeClass('dropdown-toggle') + .removeAttr('data-toggle') + .removeAttr('id') + .attr("disabled", true); + $(this).click(function(event) { + event.preventDefault(); + return; + }); + }); + $(this).find('button').each(function() { + $(this).attr("disabled", true); + }); + } else if ($(this).hasClass('input-group')) { + $(this).find('input').each(function() { + $(this).removeClass('dropdown-toggle') + .removeAttr('data-toggle') + .attr("disabled", true); + $(this).click(function(event) { + event.preventDefault(); + }); + }); + $(this).find('button').each(function() { + $(this).attr("disabled", true); + }); + } else if ($(this).hasClass('btn')) { + $(this).attr("disabled", true); + } else if ($(this).attr('data-provide', 'slider')) { + $(this).slider("disable"); + } + $(this).data("toggle", "tooltip"); + $(this).attr("title", lang_acl.prohibited); + $(this).tooltip(); + }); + + // disable submit after submitting form (not API driven buttons) + $('form').submit(function() { + if ($('form button[type="submit"]').data('submitted') == '1') { + return false; + } else { + $(this).find('button[type="submit"]').first().text(lang_footer.loading); + $('form button[type="submit"]').attr('data-submitted', '1'); + function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); }; + $(document).on("keydown", disableF5); + } + }); + // Textarea line numbers + $(".textarea-code").numberedtextarea({allowTabChar: true}); + // trigger container restart + $('#RestartContainer').on('show.bs.modal', function(e) { + var container = $(e.relatedTarget).data('container'); + $('#containerName').text(container); + $('#triggerRestartContainer').click(function(){ + $(this).prop("disabled",true); + $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> '); + $('#statusTriggerRestartContainer').html(lang_footer.restarting_container); + $.ajax({ + method: 'get', + url: '/inc/ajax/container_ctrl.php', + timeout: docker_timeout, + data: { + 'service': container, + 'action': 'restart' + } + }) + .always( function (data, status) { + $('#statusTriggerRestartContainer').append(data); + var htmlResponse = $.parseHTML(data) + if ($(htmlResponse).find('span').hasClass('text-success')) { + $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-ok"></span> '); + setTimeout(function(){ + $('#RestartContainer').modal('toggle'); + window.location = window.location.href.split("#")[0]; + }, 1200); + } else { + $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-remove"></span> '); + } + }) + }); + }) +}); \ No newline at end of file diff --git a/data/web/js/html5shiv.min.js b/data/web/js/html5shiv.min.js deleted file mode 100644 index 355afd10..00000000 --- a/data/web/js/html5shiv.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/** -* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed -*/ -!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/data/web/js/respond.min.js b/data/web/js/respond.min.js deleted file mode 100644 index 80a7b69d..00000000 --- a/data/web/js/respond.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl - * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT - * */ - -!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b<s.length;b++){var c=s[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!o[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(v(c.styleSheet.rawCssText,e,f),o[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!r||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}w()};x(),c.update=x,c.getEmValue=t,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this); \ No newline at end of file diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js new file mode 100644 index 00000000..cb124dd3 --- /dev/null +++ b/data/web/js/site/admin.js @@ -0,0 +1,395 @@ +// Base64 functions +var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; +jQuery(function($){ + // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery + var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; + function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} + function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} + function hashCode(t){for(var n=0,r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return n} + function intToRGB(t){var n=(16777215&t).toString(16).toUpperCase();return"00000".substring(0,6-n.length)+n} + $("#rspamd_preset_1").on('click', function(e) { + e.preventDefault(); + $("form[data-id=rsetting]").find("#adminRspamdSettingsDesc").val(lang.rsettings_preset_1); + $("form[data-id=rsetting]").find("#adminRspamdSettingsContent").val('priority = 10;\nauthenticated = yes;\napply "default" {\n symbols_enabled = ["DKIM_SIGNED", "RATELIMITED", "RATELIMIT_UPDATE", "RATELIMIT_CHECK", "DYN_RL_CHECK", "HISTORY_SAVE", "MILTER_HEADERS", "ARC_SIGNED"];\n}'); + }); + $("#rspamd_preset_2").on('click', function(e) { + e.preventDefault(); + $("form[data-id=rsetting]").find("#adminRspamdSettingsDesc").val(lang.rsettings_preset_2); + $("form[data-id=rsetting]").find("#adminRspamdSettingsContent").val('priority = 10;\nrcpt = "/postmaster@.*/";\nwant_spam = yes;'); + }); + $("#dkim_missing_keys").on('click', function(e) { + e.preventDefault(); + var domains = []; + $('.dkim_missing').each(function() { + domains.push($(this).val()); + }); + $('#dkim_add_domains').val(domains); + }); + $(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); }); + $("#mass_exclude").change(function(){ $("#mass_include").selectpicker('deselectAll'); }); + $("#mass_include").change(function(){ $("#mass_exclude").selectpicker('deselectAll'); }); + $("#mass_disarm").click(function() { $("#mass_send").attr("disabled", !this.checked); }); + $("#super_delete").click(function() { return confirm(lang.queue_ays); }); + $(".refresh_table").on('click', function(e) { + e.preventDefault(); + var table_name = $(this).data('table'); + $('#' + table_name).find("tr.footable-empty").remove(); + draw_table = $(this).data('draw'); + eval(draw_table + '()'); + }); + if (localStorage.getItem("current_page") === null) { + var current_page = {}; + } else { + var current_page = JSON.parse(localStorage.getItem('current_page')); + } + function table_admin_ready(ft, name) { + heading = ft.$el.parents('.tab-pane').find('.panel-heading') + var ft_paging = ft.use(FooTable.Paging) + $(heading).children('.table-lines').text(function(){ + return ft_paging.totalRows; + }) + } + function draw_domain_admins() { + ft_domainadmins = FooTable.init('#domainadminstable', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}}, + {"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"}, + {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/domain-admin/all', + jsonp: false, + error: function () { + console.log('Cannot draw domain admin table'); + }, + success: function (data) { + return process_table_data(data, 'domainadminstable'); + } + }), + "empty": lang.empty, + "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, + "state": {"enabled": true}, + "filtering": {"enabled": true,"delay": 1,"position": "left","connectors": false,"placeholder": lang.filter_table}, + "sorting": {"enabled": true} + }); + } + function draw_admins() { + ft_admins = FooTable.init('#adminstable', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"usr","title":lang.username,"style":{"width":"250px"}}, + {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/admin/all', + jsonp: false, + error: function () { + console.log('Cannot draw admin table'); + }, + success: function (data) { + return process_table_data(data, 'adminstable'); + } + }), + "empty": lang.empty, + "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, + "filtering": {"enabled": false}, + "state": {"enabled": true}, + "sorting": {"enabled": true} + }); + } + function draw_fwd_hosts() { + ft_forwardinghoststable = FooTable.init('#forwardinghoststable', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"host","type":"text","title":lang.host,"style":{"width":"250px"}}, + {"name":"source","title":lang.source,"breakpoints":"xs sm"}, + {"name":"keep_spam","title":lang.spamfilter, "type": "text","style":{"maxWidth":"80px","width":"80px"}}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/fwdhost/all', + jsonp: false, + error: function () { + console.log('Cannot draw forwarding hosts table'); + }, + success: function (data) { + return process_table_data(data, 'forwardinghoststable'); + } + }), + "empty": lang.empty, + "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, + "sorting": {"enabled": true} + }); + } + function draw_relayhosts() { + ft_relayhoststable = FooTable.init('#relayhoststable', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, + {"name":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}}, + {"name":"username","title":lang.username,"breakpoints":"xs sm"}, + {"name":"used_by_domains","title":lang.in_use_by,"style":{"width":"110px"}, "type": "text","breakpoints":"xs sm"}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} + ], + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/relayhost/all', + jsonp: false, + error: function () { + console.log('Cannot draw forwarding hosts table'); + }, + success: function (data) { + return process_table_data(data, 'relayhoststable'); + } + }), + "empty": lang.empty, + "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, + "sorting": {"enabled": true} + }); + } + function draw_transport_maps() { + ft_relayhoststable = FooTable.init('#transportstable', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, + {"name":"destination","type":"text","title":lang.destination,"style":{"width":"250px"}}, + {"name":"nexthop","type":"text","title":lang.nexthop,"style":{"width":"250px"}}, + {"name":"username","title":lang.username,"breakpoints":"xs sm"}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} + ], + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/transport/all', + jsonp: false, + error: function () { + console.log('Cannot draw transports table'); + }, + success: function (data) { + return process_table_data(data, 'transportstable'); + } + }), + "empty": lang.empty, + "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, + "sorting": {"enabled": true} + }); + } + function draw_queue() { + ft_queuetable = FooTable.init('#queuetable', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"queue_id","type":"text","title":"QID","style":{"width":"50px"}}, + {"name":"queue_name","type":"text","title":"Queue","style":{"width":"120px"}}, + {"name":"arrival_time","sorted": true,"direction": "DESC","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.arrival_time,"style":{"width":"170px"}}, + {"name":"message_size","style":{"whiteSpace":"nowrap"},"title":lang.message_size,"formatter": function(value){ + return humanFileSize(value); + }}, + {"name":"sender","title":lang.sender, "type": "text","breakpoints":"xs sm"}, + {"name":"recipients","title":lang.recipients, "type": "text","style":{"word-break":"break-all","min-width":"300px"},"breakpoints":"xs sm md"}, + ], + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/mailq/all', + jsonp: false, + error: function () { + console.log('Cannot draw forwarding hosts table'); + }, + success: function (data) { + return process_table_data(data, 'queuetable'); + } + }), + "empty": lang.empty, + "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, + "sorting": {"enabled": true}, + "on": { + "ready.ft.table": function(e, ft){ + table_admin_ready(ft, 'queuetable'); + } + } + }); + } + + function process_table_data(data, table) { + if (table == 'relayhoststable') { + $.each(data, function (i, item) { + item.action = '<div class="btn-group">' + + '<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="sender-dependent" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-triangle-right"></span> Test</a>' + + '<a href="/edit/relayhost/' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-rlyhost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />'; + }); + } else if (table == 'transportstable') { + $.each(data, function (i, item) { + if (item.username) { + item.username = '<span style="border-left:3px solid #' + intToRGB(hashCode(item.nexthop)) + ';padding-left:5px;">' + item.username + '</span>'; + } + item.action = '<div class="btn-group">' + + '<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="transport-map" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-triangle-right"></span> Test</a>' + + '<a href="/edit/transport/' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-transport" data-api-url="delete/transport" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="transports" name="multi_select" value="' + item.id + '" />'; + }); + } else if (table == 'queuetable') { + $.each(data, function (i, item) { + item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />'; + rcpts = $.map(item.recipients, function(i) { + return escapeHtml(i); + }); + item.recipients = rcpts.join('<hr style="margin:1px!important">'); + }); + } else if (table == 'forwardinghoststable') { + $.each(data, function (i, item) { + item.action = '<div class="btn-group">' + + '<a href="#" data-action="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + if (item.keep_spam == "yes") { + item.keep_spam = lang.no; + } + else { + item.keep_spam = lang.yes; + } + item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />'; + }); + } else if (table == 'domainadminstable') { + $.each(data, function (i, item) { + item.selected_domains = escapeHtml(item.selected_domains); + item.selected_domains = item.selected_domains.toString().replace(/,/g, "<br>"); + item.chkbox = '<input type="checkbox" data-id="domain_admins" name="multi_select" value="' + item.username + '" />'; + item.action = '<div class="btn-group">' + + '<a href="/edit/domainadmin/' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-success"><span class="glyphicon glyphicon-user"></span> Login</a>' + + '</div>'; + }); + } else if (table == 'adminstable') { + $.each(data, function (i, item) { + if (admin_username == item.username) { + item.usr = 'โ ' + item.username; + } else { + item.usr = item.username; + } + item.chkbox = '<input type="checkbox" data-id="admins" name="multi_select" value="' + item.username + '" />'; + item.action = '<div class="btn-group">' + + '<a href="/edit/admin/' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-admin" data-api-url="delete/admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + }); + } + return data + }; + // Initial table drawings + draw_domain_admins(); + draw_admins(); + draw_fwd_hosts(); + draw_relayhosts(); + draw_transport_maps(); + draw_queue(); + // Relayhost + $('#testRelayhostModal').on('show.bs.modal', function (e) { + $('#test_relayhost_result').text("-"); + button = $(e.relatedTarget) + if (button != null) { + $('#relayhost_id').val(button.data('relayhost-id')); + } + }) + $('#test_relayhost').on('click', function (e) { + e.preventDefault(); + prev = $('#test_relayhost').text(); + $(this).prop("disabled",true); + $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> '); + $.ajax({ + type: 'GET', + url: 'inc/ajax/relay_check.php', + dataType: 'text', + data: $('#test_relayhost_form').serialize(), + complete: function (data) { + $('#test_relayhost_result').html(data.responseText); + $('#test_relayhost').prop("disabled",false); + $('#test_relayhost').text(prev); + } + }); + }) + // Transport + $('#testTransportModal').on('show.bs.modal', function (e) { + $('#test_transport_result').text("-"); + button = $(e.relatedTarget) + if (button != null) { + $('#transport_id').val(button.data('transport-id')); + $('#transport_type').val(button.data('transport-type')); + } + }) + $('#test_transport').on('click', function (e) { + e.preventDefault(); + prev = $('#test_transport').text(); + $(this).prop("disabled",true); + $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> '); + $.ajax({ + type: 'GET', + url: 'inc/ajax/transport_check.php', + dataType: 'text', + data: $('#test_transport_form').serialize(), + complete: function (data) { + $('#test_transport_result').html(data.responseText); + $('#test_transport').prop("disabled",false); + $('#test_transport').text(prev); + } + }); + }) + // DKIM private key modal + $('#showDKIMprivKey').on('show.bs.modal', function (e) { + $('#priv_key_pre').text("-"); + p_related = $(e.relatedTarget) + if (p_related != null) { + var decoded_key = Base64.decode((p_related.data('priv-key'))); + $('#priv_key_pre').text(decoded_key); + } + }) + // App links + function add_table_row(table_id) { + var row = $('<tr />'); + cols = '<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required></td>'; + cols += '<td><input class="input-sm form-control" data-id="app_links" type="text" name="href" required></td>'; + cols += '<td><a href="#" role="button" class="btn btn-xs btn-default" type="button">Remove row</a></td>'; + row.append(cols); + table_id.append(row); + } + $('#app_link_table').on('click', 'tr a', function (e) { + e.preventDefault(); + $(this).parents('tr').remove(); + }); + $('#add_app_link_row').click(function() { + add_table_row($('#app_link_table')); + }); +}); +$(window).load(function(){ + initial_width = $("#sidebar-admin").width(); + $("#scrollbox").css("width", initial_width); + if (sessionStorage.scrollTop > 70) { + $('#scrollbox').addClass('scrollboxFixed'); + } + $(window).bind('scroll', function() { + if ($(window).scrollTop() > 70) { + $('#scrollbox').addClass('scrollboxFixed'); + } else { + $('#scrollbox').removeClass('scrollboxFixed'); + } + }); +}); +function resizeScrollbox() { + on_resize_width = $("#sidebar-admin").width(); + $("#scrollbox").removeAttr("style"); + $("#scrollbox").css("width", on_resize_width); +} +$(window).on('resize', resizeScrollbox); +$('a[data-toggle="tab"]').on('shown.bs.tab', resizeScrollbox); diff --git a/data/web/js/debug.js b/data/web/js/site/debug.js similarity index 84% rename from data/web/js/debug.js rename to data/web/js/site/debug.js index bf087167..979e21c4 100644 --- a/data/web/js/debug.js +++ b/data/web/js/site/debug.js @@ -8,6 +8,8 @@ jQuery(function($){ var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} + function hashCode(t){for(var n=0,r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return n} + function intToRGB(t){var n=(16777215&t).toString(16).toUpperCase();return"00000".substring(0,6-n.length)+n} $(".refresh_table").on('click', function(e) { e.preventDefault(); var table_name = $(this).data('table'); @@ -162,6 +164,48 @@ jQuery(function($){ } }); } + function draw_rl_logs() { + ft_rl_logs = FooTable.init('#rl_log', { + "columns": [ + {"name":"indicator","title":" ","style":{"width":"50px"}}, + {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.last_applied,"style":{"width":"170px"}}, + {"name":"rl_name","title":lang.rate_name}, + {"name":"from","title":lang.sender}, + {"name":"rcpt","title":lang.recipients}, + {"name":"user","title":lang.authed_user}, + {"name":"message_id","title":"Msg ID","breakpoints": "all","style":{"word-break":"break-all"}}, + {"name":"header_from","title":"Header From","breakpoints": "all","style":{"word-break":"break-all"}}, + {"name":"header_subject","title":"Subject","breakpoints": "all","style":{"word-break":"break-all"}}, + {"name":"rl_hash","title":"Hash","breakpoints": "all","style":{"word-break":"break-all"}}, + {"name":"qid","title":"Rspamd QID","breakpoints": "all","style":{"word-break":"break-all"}}, + {"name":"ip","title":"IP","breakpoints": "all","style":{"word-break":"break-all"}}, + {"name":"action","title":lang.action,"breakpoints": "all","style":{"word-break":"break-all"}}, + ], + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/logs/ratelimited', + jsonp: false, + error: function () { + console.log('Cannot draw rl log table'); + }, + success: function (data) { + return process_table_data(data, 'rllog'); + } + }), + "empty": lang.empty, + "paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, + "filtering": {"enabled": true,"delay": 1,"position": "left","connectors": false,"placeholder": lang.filter_table}, + "sorting": {"enabled": true}, + "on": { + "ready.ft.table": function(e, ft){ + table_log_ready(ft, 'rl_logs'); + }, + "after.ft.paging": function(e, ft){ + table_log_paging(ft, 'rl_logs'); + } + } + }); + } function draw_ui_logs() { ft_api_logs = FooTable.init('#ui_logs', { "columns": [ @@ -203,7 +247,7 @@ jQuery(function($){ ft_acme_logs = FooTable.init('#acme_log', { "columns": [ {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}}, - {"name":"message","title":lang.message}, + {"name":"message","title":lang.message,"style":{"word-break":"break-all"}}, ], "rows": $.ajax({ dataType: 'json', @@ -337,11 +381,13 @@ jQuery(function($){ function drawChart() { var data = google.visualization.arrayToDataTable(graphdata); - + var body_font_color = $('body').css("color"); var options = { is3D: true, sliceVisibilityThreshold: 0, pieSliceText: 'percentage', + backgroundColor: { fill:'transparent' }, + legend: {textStyle: {color: body_font_color}}, chartArea: { left: 0, right: 0, @@ -372,7 +418,7 @@ jQuery(function($){ {"name":"unix_time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}}, {"name": "ip","title": "IP address","breakpoints": "all","style": {"minWidth": 88}}, {"name": "sender_mime","title": "From","breakpoints": "xs sm md","style": {"minWidth": 100}}, - {"name": "rcpt_mime","title": "To","breakpoints": "xs sm md","style": {"minWidth": 100}}, + {"name": "rcpt","title": "To","breakpoints": "xs sm md","style": {"minWidth": 100}}, {"name": "subject","title": "Subject","breakpoints": "all","style": {"word-break": "break-all","minWidth": 150}}, {"name": "action","title": "Action","style": {"minwidth": 82}}, {"name": "score","title": "Score","style": {"maxWidth": 110},}, @@ -416,7 +462,12 @@ jQuery(function($){ function process_table_data(data, table) { if (table == 'rspamd_history') { $.each(data, function (i, item) { - item.rcpt_mime = item.rcpt_mime.join(",​"); + if (item.rcpt_mime != "") { + item.rcpt = item.rcpt_mime.join(", "); + } + else { + item.rcpt = item.rcpt_smtp.join(", "); + } Object.keys(item.symbols).map(function(key) { var sym = item.symbols[key]; if (sym.score <= 0) { @@ -513,7 +564,11 @@ jQuery(function($){ } else if (table == 'general_syslog') { $.each(data, function (i, item) { if (item === null) { return true; } - item.message = escapeHtml(item.message); + if (item.message.match("^base64,")) { + item.message = atob(item.message.slice(7)).replace(/\\n/g, "<br />"); + } else { + item.message = escapeHtml(item.message); + } var danger_class = ["emerg", "alert", "crit", "err"]; var warning_class = ["warning", "warn"]; var info_class = ["notice", "info", "debug"]; @@ -534,6 +589,19 @@ jQuery(function($){ item.method = '<span class="label label-warning">' + item.method + '</span>'; } }); + } else if (table == 'rllog') { + $.each(data, function (i, item) { + if (item.user == null) { + item.user = "none"; + } + if (item.rl_hash == null) { + item.rl_hash = "err"; + } + item.indicator = '<span style="border-right:6px solid #' + intToRGB(hashCode(item.rl_hash)) + ';padding-left:5px;"> </span>'; + if (item.rl_hash != 'err') { + item.action = '<a href="#" data-action="delete_selected" data-id="single-hash" data-api-url="delete/rlhash" data-item="' + encodeURI(item.rl_hash) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.reset_limit + '</a>'; + } + }); } return data }; @@ -569,6 +637,7 @@ jQuery(function($){ draw_watchdog_logs(); draw_acme_logs(); draw_api_logs(); + draw_rl_logs(); draw_ui_logs(); draw_netfilter_logs(); draw_rspamd_history(); diff --git a/data/web/js/edit.js b/data/web/js/site/edit.js similarity index 72% rename from data/web/js/edit.js rename to data/web/js/site/edit.js index 2322fa9b..97c48585 100644 --- a/data/web/js/edit.js +++ b/data/web/js/site/edit.js @@ -10,37 +10,41 @@ $(document).ready(function() { }); $("#disable_sender_check").click(function( event ) { if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) { - $('#sender_acl').prop('disabled', true); - $('#sender_acl').selectpicker('refresh'); + $('#editSelectSenderACL').prop('disabled', true); + $('#editSelectSenderACL').selectpicker('refresh'); } else { - $('#sender_acl').prop('disabled', false); - $('#sender_acl').selectpicker('refresh'); + $('#editSelectSenderACL').prop('disabled', false); + $('#editSelectSenderACL').selectpicker('refresh'); } }); if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { $('#textarea_alias_goto').prop('disabled', true); } - $("#script_data").numberedtextarea({allowTabChar: true}); + + $("#mailbox-password-warning-close").click(function( event ) { + $('#mailbox-passwd-hidden-info').addClass('hidden'); + $('#mailbox-passwd-form-groups').removeClass('hidden'); + }); }); -if ($("#multiple_bookings_select").val() == "custom") { +if ($("#editSelectMultipleBookings").val() == "custom") { $("#multiple_bookings_custom_div").show(); - $("#multiple_bookings").val($("#multiple_bookings_custom").val()); + $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); } -$("#multiple_bookings_select").change(function() { - $("#multiple_bookings").val($("#multiple_bookings_select").val()); - if ($("#multiple_bookings").val() == "custom") { +$("#editSelectMultipleBookings").change(function() { + $('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val()); + if ($('input[name=multiple_bookings]').val() == "custom") { $("#multiple_bookings_custom_div").show(); } else { $("#multiple_bookings_custom_div").hide(); } }); -if ($("#sender_acl option[value='\*']:selected").length > 0){ +if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ $("#sender_acl_disabled").show(); } -$('#sender_acl').change(function() { - if ($("#sender_acl option[value='\*']:selected").length > 0){ +$('#editSelectSenderACL').change(function() { + if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ $("#sender_acl_disabled").show(); } else { @@ -48,7 +52,7 @@ $('#sender_acl').change(function() { } }); $("#multiple_bookings_custom").bind("change keypress keyup blur", function() { - $("#multiple_bookings").val($("#multiple_bookings_custom").val()); + $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); }); jQuery(function($){ // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript @@ -61,10 +65,10 @@ jQuery(function($){ "columns": [ {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false}, - {"sorted": true,"name":"value","title":lang.spamfilter_table_rule}, + {"sorted": true,"name":"value","title":lang_user.spamfilter_table_rule}, {"name":"object","title":"Scope"} ], - "empty": lang.empty, + "empty": lang_user.empty, "rows": $.ajax({ dataType: 'json', url: '/api/v1/get/policy_wl_domain/' + table_for_domain, @@ -78,7 +82,7 @@ jQuery(function($){ item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />'; } else { - item.chkbox = '<input type="checkbox" disabled title="' + lang.spamfilter_table_domain_policy + '" />'; + item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />'; } }); } @@ -98,10 +102,10 @@ jQuery(function($){ "columns": [ {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false}, - {"sorted": true,"name":"value","title":lang.spamfilter_table_rule}, + {"sorted": true,"name":"value","title":lang_user.spamfilter_table_rule}, {"name":"object","title":"Scope"} ], - "empty": lang.empty, + "empty": lang_user.empty, "rows": $.ajax({ dataType: 'json', url: '/api/v1/get/policy_bl_domain/' + table_for_domain, @@ -115,7 +119,7 @@ jQuery(function($){ item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />'; } else { - item.chkbox = '<input type="checkbox" disabled tooltip="' + lang.spamfilter_table_domain_policy + '" />'; + item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />'; } }); } diff --git a/data/web/js/index.js b/data/web/js/site/index.js similarity index 100% rename from data/web/js/index.js rename to data/web/js/site/index.js diff --git a/data/web/js/mailbox.js b/data/web/js/site/mailbox.js similarity index 67% rename from data/web/js/mailbox.js rename to data/web/js/site/mailbox.js index 95b4dc61..42b948a0 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -1,888 +1,1022 @@ -$(document).ready(function() { - FooTable.domainFilter = FooTable.Filtering.extend({ - construct: function(instance){ - this._super(instance); - var domain_list = []; - $.ajax({ - dataType: 'json', - 'async': false, - url: '/api/v1/get/domain/all', - jsonp: false, - error: function () { - domain_list.push('Cannot read domain list'); - }, - success: function (data) { - $.each(data, function (i, item) { - domain_list.push(item.domain_name); - }); - } - }); - this.domains = domain_list; - this.def = 'All Domains'; - this.$domain = null; - }, - $create: function(){ - this._super(); - var self = this, - $form_grp = $('<div/>', {'class': 'form-group'}) - .append($('<label/>', {'class': 'sr-only', text: 'Domain'})) - .prependTo(self.$form); - self.$domain = $('<select/>', { 'class': 'form-control' }) - .on('change', {self: self}, self._onDomainDropdownChanged) - .append($('<option/>', {text: self.def})) - .appendTo($form_grp); - - $.each(self.domains, function(i, domain){ - self.$domain.append($('<option/>').text(domain)); - }); - }, - _onDomainDropdownChanged: function(e){ - var self = e.data.self, - selected = $(this).val(); - if (selected !== self.def){ - self.addFilter('domain', selected, ['domain']); - } else { - self.removeFilter('domain'); - } - self.filter(); - }, - draw: function(){ - this._super(); - var domain = this.find('domain'); - if (domain instanceof FooTable.Filter){ - this.$domain.val(domain.query.val()); - } else { - this.$domain.val(this.def); - } - $(this.$domain).closest("select").selectpicker(); - } - }); - - // Auto-fill domain quota when adding new domain - auto_fill_quota = function(domain) { - $.get("/api/v1/get/domain/" + domain, function(data){ - var result = $.parseJSON(JSON.stringify(data)); - max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576); - if (max_new_mailbox_quota != '0') { - $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); - $('#addInputQuota').attr({"disabled": false, "value": "", "type": "number", "max": max_new_mailbox_quota}); - $('#addInputQuota').val(max_new_mailbox_quota); - } - else { - $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); - $('#addInputQuota').attr({"disabled": true, "value": "", "type": "text", "value": "n/a"}); - $('#addInputQuota').val(max_new_mailbox_quota); - } - }); - } - $('#addSelectDomain').on('change', function() { - auto_fill_quota($('#addSelectDomain').val()); - }); - auto_fill_quota($('#addSelectDomain').val()); - - $(".generate_password").click(function( event ) { - event.preventDefault(); - var random_passwd = Math.random().toString(36).slice(-8) - $('#password').prop('type', 'text'); - $('#password').val(random_passwd); - $('#password2').prop('type', 'text'); - $('#password2').val(random_passwd); - }); - - $(".goto_checkbox").click(function( event ) { - $("form[data-id='add_alias'] .goto_checkbox").not(this).prop('checked', false); - if ($("form[data-id='add_alias'] .goto_checkbox:checked").length > 0) { - $('#textarea_alias_goto').prop('disabled', true); - } - else { - $("#textarea_alias_goto").removeAttr('disabled'); - } - }); - $('#addAliasModal').on('show.bs.modal', function(e) { - if ($("form[data-id='add_alias'] .goto_checkbox:checked").length > 0) { - $('#textarea_alias_goto').prop('disabled', true); - } - else { - $("#textarea_alias_goto").removeAttr('disabled'); - } - }); - - // Log modal - $('#syncjobLogModal').on('show.bs.modal', function(e) { - var syncjob_id = $(e.relatedTarget).data('syncjob-id'); - $.ajax({ - url: '/inc/ajax/syncjob_logs.php', - data: { id: syncjob_id }, - dataType: 'text', - success: function(data){ - $(e.currentTarget).find('#logText').text(data); - }, - error: function(xhr, status, error) { - $(e.currentTarget).find('#logText').text(xhr.responseText); - } - }); - }); - - // Log modal - $('#dnsInfoModal').on('show.bs.modal', function(e) { - var domain = $(e.relatedTarget).data('domain'); - $('.dns-modal-body').html('<center><span style="font-size:18pt;margin:50px" class="glyphicon glyphicon-refresh glyphicon-spin"></span></center>'); - $.ajax({ - url: '/inc/ajax/dns_diagnostics.php', - data: { domain: domain }, - dataType: 'text', - success: function(data){ - $('.dns-modal-body').html(data); - }, - error: function(xhr, status, error) { - $('.dns-modal-body').html(xhr.responseText); - } - }); - }); - - // Sieve data modal - $('#sieveDataModal').on('show.bs.modal', function(e) { - var sieveScript = $(e.relatedTarget).data('sieve-script'); - $(e.currentTarget).find('#sieveDataText').html('<pre style="font-size:14px;line-height:1.1">' + sieveScript + '</pre>'); - }); - - // Set line numbers for textarea - $("#script_data").numberedtextarea({allowTabChar: true}); - // Disable submit button on script change - $('#script_data').on('keyup', function() { - $('#add_filter_btns > #add_item').attr({"disabled": true}); - $('#validation_msg').html('-'); - }); - - // Validate script data - $("#validate_sieve").click(function( event ) { - event.preventDefault(); - var script = $('#script_data').val(); - $.ajax({ - dataType: 'jsonp', - url: "/inc/ajax/sieve_validation.php", - type: "get", - data: { script: script }, - complete: function(data) { - var response = (data.responseText); - response_obj = JSON.parse(response); - if (response_obj.type == "success") { - $('#add_filter_btns > #add_item').attr({"disabled": false}); - } - mailcow_alert_box(response_obj.msg, response_obj.type); - }, - }); - }); - // $(document).on('DOMNodeInserted', '#prefilter_table', function () { - // $("#active-script").closest('td').css('background-color','#b0f0a0'); - // $("#inactive-script").closest('td').css('background-color','#b0f0a0'); - // }); - - - -}); -jQuery(function($){ - // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery - var entityMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/', - '`': '`', - '=': '=' - }; - function escapeHtml(string) { - return String(string).replace(/[&<>"'`=\/]/g, function (s) { - return entityMap[s]; - }); - } - if (localStorage.getItem("current_page") === null) { - var current_page = {}; - } else { - var current_page = JSON.parse(localStorage.getItem('current_page')); - } - // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript - function validateEmail(email) { - var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(email); - } - // Calculation human readable file sizes - function humanFileSize(bytes) { - if(Math.abs(bytes) < 1024) { - return bytes + ' B'; - } - var units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']; - var u = -1; - do { - bytes /= 1024; - ++u; - } while(Math.abs(bytes) >= 1024 && u < units.length - 1); - return bytes.toFixed(1)+' '+units[u]; - } - function unix_time_format(tm) { - var date = new Date(tm ? tm * 1000 : 0); - return date.toLocaleString(); - } - $(".refresh_table").on('click', function(e) { - e.preventDefault(); - var table_name = $(this).data('table'); - $('#' + table_name).find("tr.footable-empty").remove(); - draw_table = $(this).data('draw'); - eval(draw_table + '()'); - }); - function table_mailbox_ready(ft, name) { - heading = ft.$el.parents('.tab-pane').find('.panel-heading') - var ft_paging = ft.use(FooTable.Paging) - $(heading).children('.table-lines').text(function(){ - return ft_paging.totalRows; - }) - if (current_page[name]) { - ft_paging.goto(parseInt(current_page[name])) - } - } - function paging_mailbox_after(ft, name) { - var ft_paging = ft.use(FooTable.Paging) - current_page[name] = ft_paging.current; - localStorage.setItem('current_page', JSON.stringify(current_page)); - } - function draw_domain_table() { - ft_domain_table = FooTable.init('#domain_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, - {"sorted": true,"name":"domain_name","title":lang.domain,"style":{"width":"250px"}}, - {"name":"aliases","title":lang.aliases,"breakpoints":"xs sm"}, - {"name":"mailboxes","title":lang.mailboxes}, - {"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota,"formatter": function(value){ - res = value.split("/"); - return humanFileSize(res[0]) + " / " + humanFileSize(res[1]); - }, - "sortValue": function(value){ - res = value.split("/"); - return Number(res[0]); - }, - }, - {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm","style":{"width":"125px"}}, - {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"width":"125px"}}, - {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm"}, - {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/domain/all', - jsonp: false, - error: function (data) { - console.log('Cannot draw domain table'); - }, - success: function (data) { - $.each(data, function (i, item) { - item.aliases = item.aliases_in_domain + " / " + item.max_num_aliases_for_domain; - item.mailboxes = item.mboxes_in_domain + " / " + item.max_num_mboxes_for_domain; - item.quota = item.quota_used_in_domain + "/" + item.max_quota_for_domain; - if (!item.rl) { - item.rl = 'โ'; - } else { - item.rl = $.map(item.rl, function(e){ - return e; - }).join('/1'); - } - item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); - item.chkbox = '<input type="checkbox" data-id="domain" name="multi_select" value="' + encodeURIComponent(item.domain_name) + '" />'; - item.action = '<div class="btn-group">'; - if (role == "admin") { - item.action += '<a href="/edit.php?domain=' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-domain" data-api-url="delete/domain" data-item="' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>'; - } - else { - item.action += '<a href="/edit.php?domain=' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>'; - } - item.action += '<a href="#dnsInfoModal" class="btn btn-xs btn-info" data-toggle="modal" data-domain="' + encodeURIComponent(item.domain_name) + '"><span class="glyphicon glyphicon-question-sign"></span> DNS</a></div>'; - }); - } - }), - "empty": lang.empty, - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'domain_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'domain_table'); - } - } - }); - } - function draw_mailbox_table() { - ft_mailbox_table = FooTable.init('#mailbox_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, - {"sorted": true,"name":"username","style":{"word-break":"break-all","min-width":"120px"},"title":lang.username}, - {"name":"name","title":lang.fname,"style":{"word-break":"break-all","min-width":"120px"},"breakpoints":"xs sm"}, - {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, - {"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota,"formatter": function(value){ - res = value.split("/"); - return humanFileSize(res[0]) + " / " + humanFileSize(res[1]); - }, - "sortValue": function(value){ - res = value.split("/"); - return Number(res[0]); - }, - }, - {"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm md"}, - {"name":"in_use","filterable": false,"type":"html","title":lang.in_use,"sortValue": function(value){ - return Number($(value).find(".progress-bar").attr('aria-valuenow')); - }, - }, - {"name":"messages","filterable": false,"title":lang.msg_num,"breakpoints":"xs sm md"}, - {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"width":"125px"}}, - {"name":"active","filterable": false,"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"min-width":"250px","text-align":"right"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} - ], - "empty": lang.empty, - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/mailbox/all', - jsonp: false, - error: function () { - console.log('Cannot draw mailbox table'); - }, - success: function (data) { - $.each(data, function (i, item) { - item.quota = item.quota_used + "/" + item.quota; - item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); - if (!item.rl) { - item.rl = 'โ'; - } else { - item.rl = $.map(item.rl, function(e){ - return e; - }).join('/1'); - } - item.chkbox = '<input type="checkbox" data-id="mailbox" name="multi_select" value="' + encodeURIComponent(item.username) + '" />'; - if (role == "admin") { - item.action = '<div class="btn-group">' + - '<a href="/edit.php?mailbox=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-success"><span class="glyphicon glyphicon-user"></span> Login</a>' + - '</div>'; - } - else { - item.action = '<div class="btn-group">' + - '<a href="/edit.php?mailbox=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - } - item.in_use = '<div class="progress">' + - '<div class="progress-bar progress-bar-' + item.percent_class + ' role="progressbar" aria-valuenow="' + item.percent_in_use + '" aria-valuemin="0" aria-valuemax="100" ' + - 'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>'; - item.username = escapeHtml(item.username); - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "components": { - "filtering": FooTable.domainFilter - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'mailbox_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'mailbox_table'); - } - } - }); - } - function draw_resource_table() { - ft_resource_table = FooTable.init('#resource_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, - {"sorted": true,"name":"description","title":lang.description,"style":{"width":"250px"}}, - {"name":"kind","title":lang.kind}, - {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, - {"name":"multiple_bookings","filterable": false,"style":{"maxWidth":"150px","width":"140px"},"title":lang.multiple_bookings,"breakpoints":"xs sm"}, - {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "empty": lang.empty, - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/resource/all', - jsonp: false, - error: function () { - console.log('Cannot draw resource table'); - }, - success: function (data) { - $.each(data, function (i, item) { - if (item.multiple_bookings == '0') { - item.multiple_bookings = '<span id="active-script" class="label label-success">' + lang.booking_0_short + '</span>'; - } else if (item.multiple_bookings == '-1') { - item.multiple_bookings = '<span id="active-script" class="label label-warning">' + lang.booking_lt0_short + '</span>'; - } else { - item.multiple_bookings = '<span id="active-script" class="label label-danger">' + lang.booking_custom_short + ' (' + item.multiple_bookings + ')</span>'; - } - item.action = '<div class="btn-group">' + - '<a href="/edit.php?resource=' + encodeURIComponent(item.name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-resource" data-api-url="delete/resource" data-item="' + item.name + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - item.chkbox = '<input type="checkbox" data-id="resource" name="multi_select" value="' + encodeURIComponent(item.name) + '" />'; - item.name = escapeHtml(item.name); - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "components": { - "filtering": FooTable.domainFilter - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'resource_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'resource_table'); - } - } - }); - } - function draw_bcc_table() { - ft_bcc_table = FooTable.init('#bcc_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, - {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, - {"name":"type","title":lang.bcc_type}, - {"name":"local_dest","title":lang.bcc_local_dest}, - {"name":"bcc_dest","title":lang.bcc_destinations}, - {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, - {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "empty": lang.empty, - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/bcc/all', - jsonp: false, - error: function () { - console.log('Cannot draw bcc table'); - }, - success: function (data) { - $.each(data, function (i, item) { - item.action = '<div class="btn-group">' + - '<a href="/edit.php?bcc=' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-bcc" data-api-url="delete/bcc" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - item.chkbox = '<input type="checkbox" data-id="bcc" name="multi_select" value="' + item.id + '" />'; - item.local_dest = escapeHtml(item.local_dest); - item.bcc_dest = escapeHtml(item.bcc_dest); - if (item.type == 'sender') { - item.type = '<span id="active-script" class="label label-success">Sender</span>'; - } else { - item.type = '<span id="inactive-script" class="label label-warning">Recipient</span>'; - } - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'bcc_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'bcc_table'); - } - } - }); - } - function draw_recipient_map_table() { - ft_recipient_map_table = FooTable.init('#recipient_map_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, - {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, - {"name":"recipient_map_old","title":lang.recipient_map_old}, - {"name":"recipient_map_new","title":lang.recipient_map_new}, - {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":(role == "admin" ? lang.action : ""),"breakpoints":"xs sm"} - ], - "empty": lang.empty, - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/recipient_map/all', - jsonp: false, - error: function () { - console.log('Cannot draw recipient map table'); - }, - success: function (data) { - if (role == "admin") { - $.each(data, function (i, item) { - item.recipient_map_old = escapeHtml(item.recipient_map_old); - item.recipient_map_new = escapeHtml(item.recipient_map_new); - item.action = '<div class="btn-group">' + - '<a href="/edit.php?recipient_map=' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-recipient_map" data-api-url="delete/recipient_map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - item.chkbox = '<input type="checkbox" data-id="recipient_map" name="multi_select" value="' + item.id + '" />'; - }); - } - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'recipient_map_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'recipient_map_table'); - } - } - }); - } - function draw_alias_table() { - ft_alias_table = FooTable.init('#alias_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, - {"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, - {"sorted": true,"name":"address","title":lang.alias,"style":{"width":"250px"}}, - {"name":"goto","title":lang.target_address}, - {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, - {"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "empty": lang.empty, - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/alias/all', - jsonp: false, - error: function () { - console.log('Cannot draw alias table'); - }, - success: function (data) { - $.each(data, function (i, item) { - item.action = '<div class="btn-group">' + - '<a href="/edit.php?alias=' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-alias" data-api-url="delete/alias" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - item.chkbox = '<input type="checkbox" data-id="alias" name="multi_select" value="' + encodeURIComponent(item.id) + '" />'; - item.goto = escapeHtml(item.goto.replace(/,/g, " ")); - if (item.is_catch_all == 1) { - item.address = '<div class="label label-default">Catch-All</div> ' + escapeHtml(item.address); - } - else { - item.address = escapeHtml(item.address); - } - if (item.goto == "null@localhost") { - item.goto = 'โคท <span style="font-size:12px" class="glyphicon glyphicon-trash" aria-hidden="true"></span>'; - } - else if (item.goto == "spam@localhost") { - item.goto = '<span class="label label-danger">Learn as spam</span>'; - } - else if (item.goto == "ham@localhost") { - item.goto = '<span class="label label-success">Learn as ham</span>'; - } - if (item.in_primary_domain !== "") { - item.domain = "โณ " + item.domain + " (" + item.in_primary_domain + ")"; - } - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "components": { - "filtering": FooTable.domainFilter - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'alias_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'alias_table'); - } - } - }); - } - - function draw_aliasdomain_table() { - ft_aliasdomain_table = FooTable.init('#aliasdomain_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, - {"sorted": true,"name":"alias_domain","title":lang.alias,"style":{"width":"250px"}}, - {"name":"target_domain","title":lang.target_domain}, - {"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "empty": lang.empty, - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/alias-domain/all', - jsonp: false, - error: function () { - console.log('Cannot draw alias domain table'); - }, - success: function (data) { - $.each(data, function (i, item) { - item.action = '<div class="btn-group">' + - '<a href="/edit.php?aliasdomain=' + encodeURIComponent(item.alias_domain) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-alias-domain" data-api-url="delete/alias-domain" data-item="' + encodeURIComponent(item.alias_domain) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '<a href="#dnsInfoModal" class="btn btn-xs btn-info" data-toggle="modal" data-domain="' + encodeURIComponent(item.alias_domain) + '"><span class="glyphicon glyphicon-question-sign"></span> DNS</a></div>' + - '</div>'; - item.chkbox = '<input type="checkbox" data-id="alias-domain" name="multi_select" value="' + encodeURIComponent(item.alias_domain) + '" />'; - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'aliasdomain_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'aliasdomain_table'); - } - } - }); - } - - function draw_sync_job_table() { - ft_syncjob_table = FooTable.init('#sync_job_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, - {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, - {"name":"user2","title":lang.owner}, - {"name":"server_w_port","title":"Server","breakpoints":"xs","style":{"word-break":"break-all"}}, - {"name":"exclude","title":lang.excludes,"breakpoints":"all"}, - {"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"}, - {"name":"last_run","title":lang.last_run,"breakpoints":"sm"}, - {"name":"log","title":"Log"}, - {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active}, - {"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "empty": lang.empty, - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/syncjobs/all/no_log', - jsonp: false, - error: function () { - console.log('Cannot draw sync job table'); - }, - success: function (data) { - $.each(data, function (i, item) { - item.log = '<a href="#syncjobLogModal" data-toggle="modal" data-syncjob-id="' + encodeURIComponent(item.id) + '">Open logs</a>' - item.user2 = escapeHtml(item.user2); - if (!item.exclude > 0) { - item.exclude = '-'; - } else { - item.exclude = '<code>' + item.exclude + '</code>'; - } - item.server_w_port = escapeHtml(item.user1) + '@' + item.host1 + ':' + item.port1; - item.action = '<div class="btn-group">' + - '<a href="/edit.php?syncjob=' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />'; - if (item.is_running == 1) { - item.is_running = '<span id="active-script" class="label label-success">' + lang.running + '</span>'; - } else { - item.is_running = '<span id="inactive-script" class="label label-warning">' + lang.waiting + '</span>'; - } - if (!item.last_run > 0) { - item.last_run = lang.waiting; - } - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'sync_job_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'sync_job_table'); - } - } - }); - } - - function draw_filter_table() { - ft_filter_table = FooTable.init('#filter_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, - {"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, - {"name":"active","style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, - {"name":"filter_type","style":{"maxWidth":"80px","width":"80px"},"title":"Type"}, - {"sorted": true,"name":"username","title":lang.owner,"style":{"maxWidth":"550px","width":"350px"}}, - {"name":"script_desc","title":lang.description,"breakpoints":"xs"}, - {"name":"script_data","title":"Script","breakpoints":"all"}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} - ], - "empty": lang.empty, - "rows": $.ajax({ - dataType: 'json', - url: '/api/v1/get/filters/all', - jsonp: false, - error: function () { - console.log('Cannot draw filter table'); - }, - success: function (data) { - $.each(data, function (i, item) { - if (item.active_int == 1) { - item.active = '<span id="active-script" class="label label-success">' + lang.active + '</span>'; - } else { - item.active = '<span id="inactive-script" class="label label-warning">' + lang.inactive + '</span>'; - } - item.script_data = '<pre style="margin:0px">' + escapeHtml(item.script_data) + '</pre>' - item.filter_type = '<div class="label label-default">' + item.filter_type.charAt(0).toUpperCase() + item.filter_type.slice(1).toLowerCase() + '</div>' - item.action = '<div class="btn-group">' + - '<a href="/edit.php?filter=' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-filter" data-api-url="delete/filter" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + - '</div>'; - item.chkbox = '<input type="checkbox" data-id="filter_item" name="multi_select" value="' + item.id + '" />' - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'filter_table'); - }, - "after.ft.paging": function(e, ft){ - paging_mailbox_after(ft, 'filter_table'); - } - } - }); - }; - - draw_domain_table(); - draw_mailbox_table(); - draw_resource_table(); - draw_alias_table(); - draw_aliasdomain_table(); - draw_sync_job_table(); - draw_filter_table(); - draw_bcc_table(); - draw_recipient_map_table(); - -}); +$(document).ready(function() { + acl_data = JSON.parse(acl); + FooTable.domainFilter = FooTable.Filtering.extend({ + construct: function(instance){ + this._super(instance); + var domain_list = []; + $.ajax({ + dataType: 'json', + url: '/api/v1/get/domain/all', + jsonp: false, + async: true, + error: function () { + domain_list.push('Cannot read domain list'); + }, + success: function (data) { + $.each(data, function (i, item) { + domain_list.push(item.domain_name); + }); + } + }); + this.domains = domain_list; + this.def = 'All Domains'; + this.$domain = null; + }, + $create: function(){ + this._super(); + var self = this, + $form_grp = $('<div/>', {'class': 'form-group'}) + .append($('<label/>', {'class': 'sr-only', text: 'Domain'})) + .prependTo(self.$form); + self.$domain = $('<select/>', { 'class': 'aform-control' }) + .on('change', {self: self}, self._onDomainDropdownChanged) + .append($('<option/>', {text: self.def})) + .appendTo($form_grp); + + $.each(self.domains, function(i, domain){ + self.$domain.append($('<option/>').text(domain)); + }); + }, + _onDomainDropdownChanged: function(e){ + var self = e.data.self, + selected = $(this).val(); + if (selected !== self.def){ + self.addFilter('domain', selected, ['domain']); + } else { + self.removeFilter('domain'); + } + self.filter(); + }, + draw: function(){ + this._super(); + var domain = this.find('domain'); + if (domain instanceof FooTable.Filter){ + this.$domain.val(domain.query.val()); + } else { + this.$domain.val(this.def); + } + $(this.$domain).closest("select").selectpicker(); + } + }); + // Auto-fill domain quota when adding new domain + auto_fill_quota = function(domain) { + $.get("/api/v1/get/domain/" + domain, function(data){ + var result = $.parseJSON(JSON.stringify(data)); + max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576); + if (max_new_mailbox_quota != '0') { + $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); + $('#addInputQuota').attr({"disabled": false, "value": "", "type": "number", "max": max_new_mailbox_quota}); + $('#addInputQuota').val(max_new_mailbox_quota); + } + else { + $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); + $('#addInputQuota').attr({"disabled": true, "value": "", "type": "text", "value": "n/a"}); + $('#addInputQuota').val(max_new_mailbox_quota); + } + }); + } + $('#addSelectDomain').on('change', function() { + auto_fill_quota($('#addSelectDomain').val()); + }); + auto_fill_quota($('#addSelectDomain').val()); + $(".generate_password").click(function( event ) { + event.preventDefault(); + $('[data-hibp]').trigger('input'); + var random_passwd = Math.random().toString(36).slice(-8) + $(this).closest("form").find("input[name='password']").prop('type', 'text'); + $(this).closest("form").find("input[name='password2']").prop('type', 'text'); + $(this).closest("form").find("input[name='password']").val(random_passwd); + $(this).closest("form").find("input[name='password2']").val(random_passwd); + }); + $(".goto_checkbox").click(function( event ) { + $("form[data-id='add_alias'] .goto_checkbox").not(this).prop('checked', false); + if ($("form[data-id='add_alias'] .goto_checkbox:checked").length > 0) { + $('#textarea_alias_goto').prop('disabled', true); + } + else { + $("#textarea_alias_goto").removeAttr('disabled'); + } + }); + $('#addAliasModal').on('show.bs.modal', function(e) { + if ($("form[data-id='add_alias'] .goto_checkbox:checked").length > 0) { + $('#textarea_alias_goto').prop('disabled', true); + } + else { + $("#textarea_alias_goto").removeAttr('disabled'); + } + }); + // Log modal + $('#syncjobLogModal').on('show.bs.modal', function(e) { + var syncjob_id = $(e.relatedTarget).data('syncjob-id'); + $.ajax({ + url: '/inc/ajax/syncjob_logs.php', + data: { id: syncjob_id }, + dataType: 'text', + success: function(data){ + $(e.currentTarget).find('#logText').text(data); + }, + error: function(xhr, status, error) { + $(e.currentTarget).find('#logText').text(xhr.responseText); + } + }); + }); + // Log modal + $('#dnsInfoModal').on('show.bs.modal', function(e) { + var domain = $(e.relatedTarget).data('domain'); + $('.dns-modal-body').html('<center><span style="font-size:18pt;margin:50px" class="glyphicon glyphicon-refresh glyphicon-spin"></span></center>'); + $.ajax({ + url: '/inc/ajax/dns_diagnostics.php', + data: { domain: domain }, + dataType: 'text', + success: function(data){ + $('.dns-modal-body').html(data); + }, + error: function(xhr, status, error) { + $('.dns-modal-body').html(xhr.responseText); + } + }); + }); + // Sieve data modal + $('#sieveDataModal').on('show.bs.modal', function(e) { + var sieveScript = $(e.relatedTarget).data('sieve-script'); + $(e.currentTarget).find('#sieveDataText').html('<pre style="font-size:14px;line-height:1.1">' + sieveScript + '</pre>'); + }); + // Disable submit button on script change + $('#script_data').on('keyup', function() { + $('#add_filter_btns > #add_sieve_script').attr({"disabled": true}); + $('#validation_msg').html('-'); + }); + // Validate script data + $("#validate_sieve").click(function( event ) { + event.preventDefault(); + var script = $('#script_data').val(); + $.ajax({ + dataType: 'jsonp', + url: "/inc/ajax/sieve_validation.php", + type: "get", + data: { script: script }, + complete: function(data) { + var response = (data.responseText); + response_obj = JSON.parse(response); + if (response_obj.type == "success") { + $('#add_filter_btns > #add_sieve_script').attr({"disabled": false}); + } + mailcow_alert_box(response_obj.msg, response_obj.type); + }, + }); + }); + // $(document).on('DOMNodeInserted', '#prefilter_table', function () { + // $("#active-script").closest('td').css('background-color','#b0f0a0'); + // $("#inactive-script").closest('td').css('background-color','#b0f0a0'); + // }); + $('#addResourceModal').on('shown.bs.modal', function() { + $("#multiple_bookings").val($("#multiple_bookings_select").val()); + if ($("#multiple_bookings").val() == "custom") { + $("#multiple_bookings_custom_div").show(); + $("#multiple_bookings").val($("#multiple_bookings_custom").val()); + } + }) + $("#multiple_bookings_select").change(function() { + $("#multiple_bookings").val($("#multiple_bookings_select").val()); + if ($("#multiple_bookings").val() == "custom") { + $("#multiple_bookings_custom_div").show(); + } + else { + $("#multiple_bookings_custom_div").hide(); + } + }); + $("#multiple_bookings_custom").bind ("change keypress keyup blur", function () { + $("#multiple_bookings").val($("#multiple_bookings_custom").val()); + }); + + +}); +jQuery(function($){ + // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery + var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; + function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} + // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript + function validateEmail(email) { + var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} + $(".refresh_table").on('click', function(e) { + e.preventDefault(); + var table_name = $(this).data('table'); + $('#' + table_name).find("tr.footable-empty").remove(); + draw_table = $(this).data('draw'); + eval(draw_table + '()'); + }); + function table_mailbox_ready(ft, name) { + if(is_dual) { + $('.login_as').data("toggle", "tooltip") + .attr("disabled", true) + .removeAttr("href") + .attr("title", "Dual login cannot be used twice") + .tooltip(); + } + heading = ft.$el.parents('.tab-pane').find('.panel-heading') + var ft_paging = ft.use(FooTable.Paging) + $(heading).children('.table-lines').text(function(){ + return ft_paging.totalRows; + }) + } + function draw_domain_table() { + ft_domain_table = FooTable.init('#domain_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"domain_name","title":lang.domain,"style":{"width":"250px"}}, + {"name":"aliases","title":lang.aliases,"breakpoints":"xs sm"}, + {"name":"mailboxes","title":lang.mailboxes}, + {"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota,"formatter": function(value){ + res = value.split("/"); + return humanFileSize(res[0]) + " / " + humanFileSize(res[1]); + }, + "sortValue": function(value){ + res = value.split("/"); + return Number(res[0]); + }, + }, + {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm","style":{"width":"125px"}}, + {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"maxWidth":"100px","width":"100px"}}, + {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm md"}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} + ], + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/domain/all', + jsonp: false, + error: function (data) { + console.log('Cannot draw domain table'); + }, + success: function (data) { + $.each(data, function (i, item) { + item.aliases = item.aliases_in_domain + " / " + item.max_num_aliases_for_domain; + item.mailboxes = item.mboxes_in_domain + " / " + item.max_num_mboxes_for_domain; + item.quota = item.quota_used_in_domain + "/" + item.max_quota_for_domain; + if (!item.rl) { + item.rl = 'โ'; + } else { + item.rl = $.map(item.rl, function(e){ + return e; + }).join('/1'); + } + item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); + item.chkbox = '<input type="checkbox" data-id="domain" name="multi_select" value="' + encodeURIComponent(item.domain_name) + '" />'; + item.action = '<div class="btn-group">'; + if (role == "admin") { + item.action += '<a href="/edit/domain/' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-domain" data-api-url="delete/domain" data-item="' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>'; + } + else { + item.action += '<a href="/edit/domain/' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>'; + } + item.action += '<a href="#dnsInfoModal" class="btn btn-xs btn-info" data-toggle="modal" data-domain="' + encodeURIComponent(item.domain_name) + '"><span class="glyphicon glyphicon-question-sign"></span> DNS</a></div>'; + }); + } + }), + "empty": lang.empty, + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'domain_table'); + } + } + }); + } + function draw_mailbox_table() { + ft_mailbox_table = FooTable.init('#mailbox_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"username","style":{"word-break":"break-all","min-width":"120px"},"title":lang.username}, + {"name":"name","title":lang.fname,"style":{"word-break":"break-all","min-width":"120px"},"breakpoints":"xs sm"}, + {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, + {"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota,"formatter": function(value){ + res = value.split("/"); + var of_q = (res[1] == 0 ? "โ" : humanFileSize(res[1])); + return humanFileSize(res[0]) + " / " + of_q; + }, + "sortValue": function(value){ + res = value.split("/"); + return Number(res[0]); + }, + }, + {"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm md"}, + {"name":"tls_enforce_in","filterable": false,"title":lang.tls_enforce_in,"breakpoints":"all"}, + {"name":"tls_enforce_out","filterable": false,"title":lang.tls_enforce_out,"breakpoints":"all"}, + {"name":"quarantine_notification","filterable": false,"title":lang.quarantine_notification,"breakpoints":"all"}, + {"name":"in_use","filterable": false,"type":"html","title":lang.in_use,"sortValue": function(value){ + return Number($(value).find(".progress-bar").attr('aria-valuenow')); + }, + }, + {"name":"messages","filterable": false,"title":lang.msg_num,"breakpoints":"xs sm md"}, + {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"width":"125px"}}, + {"name":"active","filterable": false,"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"min-width":"290px","text-align":"right"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/mailbox/all', + jsonp: false, + error: function () { + console.log('Cannot draw mailbox table'); + }, + success: function (data) { + $.each(data, function (i, item) { + item.quota = item.quota_used + "/" + item.quota; + item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); + if (!item.rl) { + item.rl = 'โ'; + } else { + item.rl = $.map(item.rl, function(e){ + return e; + }).join('/1'); + } + item.chkbox = '<input type="checkbox" data-id="mailbox" name="multi_select" value="' + encodeURIComponent(item.username) + '" />'; + item.tls_enforce_in = '<span class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success' : 'danger') + ' glyphicon glyphicon-lock"></span>'; + item.tls_enforce_out = '<span class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success' : 'danger') + ' glyphicon glyphicon-lock"></span>'; + if (item.attributes.quarantine_notification === 'never') { + item.quarantine_notification = lang.never; + } else if (item.attributes.quarantine_notification === 'hourly') { + item.quarantine_notification = lang.hourly; + } else if (item.attributes.quarantine_notification === 'daily') { + item.quarantine_notification = lang.daily; + } else if (item.attributes.quarantine_notification === 'weekly') { + item.quarantine_notification = lang.weekly; + } + if (acl_data.login_as === 1) { + item.action = '<div class="btn-group">' + + '<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="login_as btn btn-xs btn-success"><span class="glyphicon glyphicon-user"></span> Login</a>'; + if (ALLOW_ADMIN_EMAIL_LOGIN) { + item.action += '<a href="/sogo-auth.php?login=' + encodeURIComponent(item.username) + '" class="login_as btn btn-xs btn-primary" target="_blank"><span class="glyphicon glyphicon-envelope"></span> SOGo</a>'; + } + item.action += '</div>'; + } + else { + item.action = '<div class="btn-group">' + + '<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + } + item.in_use = '<div class="progress">' + + '<div class="progress-bar progress-bar-' + item.percent_class + ' role="progressbar" aria-valuenow="' + item.percent_in_use + '" aria-valuemin="0" aria-valuemax="100" ' + + 'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>'; + item.username = escapeHtml(item.username); + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + //"container": "#tab-mailboxes.panel", + "placeholder": lang.filter_table + }, + "components": { + "filtering": FooTable.domainFilter + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'mailbox_table'); + } + } + }); + } + function draw_resource_table() { + ft_resource_table = FooTable.init('#resource_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"description","title":lang.description,"style":{"width":"250px"}}, + {"name":"kind","title":lang.kind}, + {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, + {"name":"multiple_bookings","filterable": false,"style":{"maxWidth":"150px","width":"140px"},"title":lang.multiple_bookings,"breakpoints":"xs sm"}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/resource/all', + jsonp: false, + error: function () { + console.log('Cannot draw resource table'); + }, + success: function (data) { + $.each(data, function (i, item) { + if (item.multiple_bookings == '0') { + item.multiple_bookings = '<span id="active-script" class="label label-success">' + lang.booking_0_short + '</span>'; + } else if (item.multiple_bookings == '-1') { + item.multiple_bookings = '<span id="active-script" class="label label-warning">' + lang.booking_lt0_short + '</span>'; + } else { + item.multiple_bookings = '<span id="active-script" class="label label-danger">' + lang.booking_custom_short + ' (' + item.multiple_bookings + ')</span>'; + } + item.action = '<div class="btn-group">' + + '<a href="/edit/resource/' + encodeURIComponent(item.name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-resource" data-api-url="delete/resource" data-item="' + item.name + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="resource" name="multi_select" value="' + encodeURIComponent(item.name) + '" />'; + item.name = escapeHtml(item.name); + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "components": { + "filtering": FooTable.domainFilter + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'resource_table'); + } + } + }); + } + function draw_bcc_table() { + ft_bcc_table = FooTable.init('#bcc_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"name":"type","title":lang.bcc_type}, + {"name":"local_dest","title":lang.bcc_local_dest}, + {"name":"bcc_dest","title":lang.bcc_destinations}, + {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/bcc/all', + jsonp: false, + error: function () { + console.log('Cannot draw bcc table'); + }, + success: function (data) { + $.each(data, function (i, item) { + item.action = '<div class="btn-group">' + + '<a href="/edit/bcc/' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-bcc" data-api-url="delete/bcc" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="bcc" name="multi_select" value="' + item.id + '" />'; + item.local_dest = escapeHtml(item.local_dest); + item.bcc_dest = escapeHtml(item.bcc_dest); + if (item.type == 'sender') { + item.type = '<span id="active-script" class="label label-success">Sender</span>'; + } else { + item.type = '<span id="inactive-script" class="label label-warning">Recipient</span>'; + } + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'bcc_table'); + } + } + }); + } + function draw_recipient_map_table() { + ft_recipient_map_table = FooTable.init('#recipient_map_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"name":"recipient_map_old","title":lang.recipient_map_old}, + {"name":"recipient_map_new","title":lang.recipient_map_new}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":(role == "admin" ? lang.action : ""),"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/recipient_map/all', + jsonp: false, + error: function () { + console.log('Cannot draw recipient map table'); + }, + success: function (data) { + if (role == "admin") { + $.each(data, function (i, item) { + item.recipient_map_old = escapeHtml(item.recipient_map_old); + item.recipient_map_new = escapeHtml(item.recipient_map_new); + item.action = '<div class="btn-group">' + + '<a href="/edit/recipient_map/' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-recipient_map" data-api-url="delete/recipient_map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="recipient_map" name="multi_select" value="' + item.id + '" />'; + }); + } + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'recipient_map_table'); + } + } + }); + } + function draw_tls_policy_table() { + ft_tls_policy_table = FooTable.init('#tls_policy_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"name":"dest","title":lang.tls_map_dest}, + {"name":"policy","title":lang.tls_map_policy}, + {"name":"parameters","title":lang.tls_map_parameters}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":(role == "admin" ? lang.action : ""),"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/tls-policy-map/all', + jsonp: false, + error: function () { + console.log('Cannot draw tls policy map table'); + }, + success: function (data) { + if (role == "admin") { + $.each(data, function (i, item) { + item.dest = escapeHtml(item.dest); + item.policy = '<b>' + escapeHtml(item.policy) + '</b>'; + if (item.parameters == '') { + item.parameters = '<code>-</code>'; + } else { + item.parameters = '<code>' + escapeHtml(item.parameters) + '</code>'; + } + item.action = '<div class="btn-group">' + + '<a href="/edit/tls_policy_map/' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-tls-policy-map" data-api-url="delete/tls-policy-map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="tls-policy-map" name="multi_select" value="' + item.id + '" />'; + }); + } + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'tls_policy_table'); + } + } + }); + } + function draw_transport_maps_table() { + ft_transport_maps_table = FooTable.init('#transport_maps_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"name":"dest","title":lang.tls_map_dest}, + {"name":"parameters","title":lang.tls_map_parameters}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":(role == "admin" ? lang.action : ""),"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/transport-map/all', + jsonp: false, + error: function () { + console.log('Cannot draw transport map table'); + }, + success: function (data) { + if (role == "admin") { + $.each(data, function (i, item) { + item.dest = escapeHtml(item.dest); + if (item.parameters == '') { + item.parameters = '<code>-</code>'; + } else { + item.parameters = '<code>' + escapeHtml(item.parameters) + '</code>'; + } + item.action = '<div class="btn-group">' + + '<a href="/edit/transport_map/' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-transport-map" data-api-url="delete/transport-map" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="transport-map" name="multi_select" value="' + item.id + '" />'; + }); + } + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'transport_maps_table'); + } + } + }); + } + function draw_alias_table() { + ft_alias_table = FooTable.init('#alias_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"sorted": true,"name":"address","title":lang.alias,"style":{"width":"250px"}}, + {"name":"goto","title":lang.target_address}, + {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, + {"name":"public_comment","title":lang.public_comment,"breakpoints":"all"}, + {"name":"private_comment","title":lang.private_comment,"breakpoints":"all"}, + {"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/alias/all', + jsonp: false, + error: function () { + console.log('Cannot draw alias table'); + }, + success: function (data) { + $.each(data, function (i, item) { + item.action = '<div class="btn-group">' + + '<a href="/edit/alias/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-alias" data-api-url="delete/alias" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="alias" name="multi_select" value="' + encodeURIComponent(item.id) + '" />'; + item.goto = escapeHtml(item.goto.replace(/,/g, " ")); + if (item.public_comment !== null) { + item.public_comment = escapeHtml(item.public_comment); + } + else { + item.public_comment = '-'; + } + if (item.private_comment !== null) { + item.private_comment = escapeHtml(item.private_comment); + } + else { + item.private_comment = '-'; + } + if (item.is_catch_all == 1) { + item.address = '<div class="label label-default">Catch-All</div> ' + escapeHtml(item.address); + } + else { + item.address = escapeHtml(item.address); + } + if (item.goto == "null@localhost") { + item.goto = 'โคท <span style="font-size:12px" class="glyphicon glyphicon-trash" aria-hidden="true"></span>'; + } + else if (item.goto == "spam@localhost") { + item.goto = '<span class="label label-danger">Learn as spam</span>'; + } + else if (item.goto == "ham@localhost") { + item.goto = '<span class="label label-success">Learn as ham</span>'; + } + if (item.in_primary_domain !== "") { + item.domain = "โณ " + item.domain + " (" + item.in_primary_domain + ")"; + } + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "components": { + "filtering": FooTable.domainFilter + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'alias_table'); + } + } + }); + } + + function draw_aliasdomain_table() { + ft_aliasdomain_table = FooTable.init('#aliasdomain_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"alias_domain","title":lang.alias,"style":{"width":"250px"}}, + {"name":"target_domain","title":lang.target_domain}, + {"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/alias-domain/all', + jsonp: false, + error: function () { + console.log('Cannot draw alias domain table'); + }, + success: function (data) { + $.each(data, function (i, item) { + item.action = '<div class="btn-group">' + + '<a href="/edit/aliasdomain/' + encodeURIComponent(item.alias_domain) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-alias-domain" data-api-url="delete/alias-domain" data-item="' + encodeURIComponent(item.alias_domain) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '<a href="#dnsInfoModal" class="btn btn-xs btn-info" data-toggle="modal" data-domain="' + encodeURIComponent(item.alias_domain) + '"><span class="glyphicon glyphicon-question-sign"></span> DNS</a></div>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="alias-domain" name="multi_select" value="' + encodeURIComponent(item.alias_domain) + '" />'; + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'aliasdomain_table'); + } + } + }); + } + + function draw_sync_job_table() { + ft_syncjob_table = FooTable.init('#sync_job_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"name":"user2","title":lang.owner}, + {"name":"server_w_port","title":"Server","breakpoints":"xs","style":{"word-break":"break-all"}}, + {"name":"exclude","title":lang.excludes,"breakpoints":"all"}, + {"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"}, + {"name":"last_run","title":lang.last_run,"breakpoints":"sm"}, + {"name":"log","title":"Log"}, + {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active}, + {"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/syncjobs/all/no_log', + jsonp: false, + error: function () { + console.log('Cannot draw sync job table'); + }, + success: function (data) { + $.each(data, function (i, item) { + item.log = '<a href="#syncjobLogModal" data-toggle="modal" data-syncjob-id="' + encodeURIComponent(item.id) + '">Open logs</a>' + item.user2 = escapeHtml(item.user2); + if (!item.exclude > 0) { + item.exclude = '-'; + } else { + item.exclude = '<code>' + item.exclude + '</code>'; + } + item.server_w_port = escapeHtml(item.user1) + '@' + item.host1 + ':' + item.port1; + item.action = '<div class="btn-group">' + + '<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />'; + if (item.is_running == 1) { + item.is_running = '<span id="active-script" class="label label-success">' + lang.running + '</span>'; + } else { + item.is_running = '<span id="inactive-script" class="label label-warning">' + lang.waiting + '</span>'; + } + if (!item.last_run > 0) { + item.last_run = lang.waiting; + } + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'sync_job_table'); + } + } + }); + } + + function draw_filter_table() { + ft_filter_table = FooTable.init('#filter_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"name":"active","style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"filter_type","style":{"maxWidth":"80px","width":"80px"},"title":"Type"}, + {"sorted": true,"name":"username","title":lang.owner,"style":{"maxWidth":"550px","width":"350px"}}, + {"name":"script_desc","title":lang.description,"breakpoints":"xs"}, + {"name":"script_data","title":"Script","breakpoints":"all"}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/filters/all', + jsonp: false, + error: function () { + console.log('Cannot draw filter table'); + }, + success: function (data) { + $.each(data, function (i, item) { + if (item.active_int == 1) { + item.active = '<span id="active-script" class="label label-success">' + lang.active + '</span>'; + } else { + item.active = '<span id="inactive-script" class="label label-warning">' + lang.inactive + '</span>'; + } + item.script_data = '<pre style="margin:0px">' + escapeHtml(item.script_data) + '</pre>' + item.filter_type = '<div class="label label-default">' + item.filter_type.charAt(0).toUpperCase() + item.filter_type.slice(1).toLowerCase() + '</div>' + item.action = '<div class="btn-group">' + + '<a href="/edit/filter/' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-filter" data-api-url="delete/filter" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '</div>'; + item.chkbox = '<input type="checkbox" data-id="filter_item" name="multi_select" value="' + item.id + '" />' + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'filter_table'); + } + } + }); + }; + + draw_domain_table(); + draw_mailbox_table(); + draw_resource_table(); + draw_alias_table(); + draw_aliasdomain_table(); + draw_sync_job_table(); + draw_filter_table(); + draw_bcc_table(); + draw_recipient_map_table(); + draw_tls_policy_table(); + draw_transport_maps_table(); + +}); \ No newline at end of file diff --git a/data/web/js/quarantine.js b/data/web/js/site/quarantine.js similarity index 54% rename from data/web/js/quarantine.js rename to data/web/js/site/quarantine.js index 21ec9ddc..4df1dbd4 100644 --- a/data/web/js/quarantine.js +++ b/data/web/js/site/quarantine.js @@ -1,6 +1,8 @@ // Base64 functions var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; + jQuery(function($){ + acl_data = JSON.parse(acl); // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} @@ -9,13 +11,15 @@ jQuery(function($){ function draw_quarantine_table() { ft_quarantinetable = FooTable.init('#quarantinetable', { "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, {"name":"id","type":"ID","filterable": false,"sorted": true,"direction":"DESC","title":"ID","style":{"width":"50px"}}, - {"name":"qid","type":"text","title":lang.qid,"style":{"width":"125px"}}, - {"name":"sender","style":{"word-break":"break-all"},"title":lang.sender,"breakpoints":"xs sm"}, - {"name":"rcpt","title":lang.rcpt, "type": "text"}, + {"name":"qid","breakpoints":"all","type":"text","title":lang.qid,"style":{"width":"125px"}}, + {"name":"sender","title":lang.sender}, + {"name":"rcpt","title":lang.rcpt, "breakpoints":"xs sm md", "type": "text"}, + {"name":"virus","title":lang.danger, "type": "text"}, + {"name":"subject","title":lang.subj, "type": "text"}, {"name":"created","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.received,"style":{"width":"170px"}}, - {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} ], "rows": $.ajax({ dataType: 'json', @@ -26,10 +30,27 @@ jQuery(function($){ }, success: function (data) { $.each(data, function (i, item) { + if (item.subject === null) { + item.subject = ''; + } else { + item.subject = escapeHtml(item.subject); + } + if (item.virus_flag > 0) { + item.virus = '<span class="dot-danger"></span>'; + } else { + item.virus = '<span class="dot-neutral"></span>'; + } + if (acl_data.login_as === 1) { item.action = '<div class="btn-group">' + '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><span class="glyphicon glyphicon-modal-window"></span> ' + lang.show_item + '</a>' + - '<a href="#" id="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + '</div>'; + } + else { + item.action = '<div class="btn-group">' + + '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><span class="glyphicon glyphicon-modal-window"></span> ' + lang.show_item + '</a>' + + '</div>'; + } item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />'; }); } @@ -37,48 +58,52 @@ jQuery(function($){ "empty": lang.empty, "paging": {"enabled": true,"limit": 5,"size": pagination_size}, "sorting": {"enabled": true}, - "on": { - "ready.ft.table": btn_group_quarantine, - "after.ft.paging": btn_group_quarantine - }, "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table}, }); } - btn_group_quarantine = function(ev, ft){ - $('.show_qid_info').on('click', function (e) { - e.preventDefault(); - var qitem = $(this).data('item'); - $('#qidDetailModal').modal('show'); - $( "#qid_error" ).hide(); - $.ajax({ - url: '/inc/ajax/qitem_details.php', - data: { id: qitem }, - dataType: 'json', - success: function(data){ - if (typeof data.error !== 'undefined') { - $( "#qid_error" ).text(data.error); - $( "#qid_error" ).show(); - } - $('#qid_detail_subj').text(data.subject); - $('#qid_detail_text').text(data.text_plain); - $('#qid_detail_text_from_html').text(data.text_html); - if (typeof data.attachments !== 'undefined') { - $( "#qid_detail_atts" ).text(''); - $.each(data.attachments, function( index, value ) { - $( "#qid_detail_atts" ).append( - '<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' + - ' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>' - ); - }); - } - else { - $( "#qid_detail_atts" ).text('-'); - } + $('body').on('click', '.show_qid_info', function (e) { + e.preventDefault(); + var qitem = $(this).data('item'); + var qError = $("#qid_error"); + + $('#qidDetailModal').modal('show'); + qError.hide(); + + $.ajax({ + url: '/inc/ajax/qitem_details.php', + data: { id: qitem }, + dataType: 'json', + success: function(data){ + if (typeof data.error !== 'undefined') { + qError.text(data.error); + qError.show(); } - }); - }) - } + $('[data-id="qitems_single"]').each(function(index) { + $(this).attr("data-item", qitem); + }); + + $('#qid_detail_subj').text(data.subject); + $('#qid_detail_text').text(data.text_plain); + $('#qid_detail_text_from_html').text(data.text_html); + + if (typeof data.attachments !== 'undefined') { + qAtts = $("#qid_detail_atts"); + qAtts.text(''); + $.each(data.attachments, function(index, value) { + qAtts.append( + '<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' + + ' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>' + ); + }); + } + else { + qAtts.text('-'); + } + } + }); + }); + // Initial table drawings draw_quarantine_table(); }); diff --git a/data/web/js/user.js b/data/web/js/site/user.js similarity index 90% rename from data/web/js/user.js rename to data/web/js/site/user.js index 226d039a..505da772 100644 --- a/data/web/js/user.js +++ b/data/web/js/site/user.js @@ -64,7 +64,7 @@ jQuery(function($){ $.each(data, function (i, item) { if (acl_data.spam_alias === 1) { item.action = '<div class="btn-group">' + - '<a href="#" id="delete_selected" data-id="single-tla" data-api-url="delete/time_limited_alias" data-item="' + encodeURIComponent(item.address) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-tla" data-api-url="delete/time_limited_alias" data-item="' + encodeURIComponent(item.address) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + '</div>'; item.chkbox = '<input type="checkbox" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />'; item.address = escapeHtml(item.address); @@ -81,6 +81,7 @@ jQuery(function($){ "limit": 5, "size": pagination_size }, + "state": {"enabled": true}, "sorting": { "enabled": true } @@ -122,8 +123,8 @@ jQuery(function($){ item.server_w_port = escapeHtml(item.user1 + '@' + item.host1 + ':' + item.port1); if (acl_data.syncjobs === 1) { item.action = '<div class="btn-group">' + - '<a href="/edit.php?syncjob=' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + - '<a href="#" id="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + + '<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' + + '<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' + '</div>'; item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />'; } @@ -147,6 +148,7 @@ jQuery(function($){ "limit": 5, "size": pagination_size }, + "state": {"enabled": true}, "sorting": { "enabled": true } @@ -182,6 +184,7 @@ jQuery(function($){ }); } }), + "state": {"enabled": true}, "paging": { "enabled": true, "limit": 5, @@ -227,6 +230,7 @@ jQuery(function($){ "limit": 5, "size": pagination_size }, + "state": {"enabled": true}, "sorting": { "enabled": true } diff --git a/data/web/json_api.php b/data/web/json_api.php index fba933c4..53ecf520 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -102,6 +102,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "relayhost": process_add_return(relayhost('add', $attr)); break; + case "transport": + process_add_return(transport('add', $attr)); + break; case "rsetting": process_add_return(rsettings('add', $attr)); break; @@ -144,6 +147,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "domain-admin": process_add_return(domain_admin('add', $attr)); break; + case "admin": + process_add_return(admin('add', $attr)); + break; case "syncjob": process_add_return(mailbox('add', 'syncjob', $attr)); break; @@ -153,6 +159,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "recipient_map": process_add_return(recipient_map('add', $attr)); break; + case "tls-policy-map": + process_add_return(tls_policy_maps('add', $attr)); + break; } break; case "get": @@ -164,7 +173,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u switch ($object) { case "actions": $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/rspamd-sock/rspamd.sock'); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat"); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $data = curl_exec($curl); @@ -212,6 +221,20 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u } break; + case "mailq": + switch ($object) { + case "all": + $mailq = mailq('get'); + if (!empty($mailq)) { + echo $mailq; + } + else { + echo '{}'; + } + break; + } + break; + case "rl-domain": switch ($object) { case "all": @@ -300,6 +323,33 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u } break; + case "transport": + switch ($object) { + case "all": + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + if ($details = transport('details', $transport['id'])) { + $data[] = $details; + } + else { + continue; + } + } + process_get_return($data); + } + else { + echo '{}'; + } + break; + + default: + $data = transport('details', $object); + process_get_return($data); + break; + } + break; + case "rsetting": switch ($object) { case "all": @@ -367,6 +417,17 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u } echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}'; break; + case "ratelimited": + // 0 is first record, so empty is fine + if (isset($extra)) { + $extra = preg_replace('/[^\d\-]/i', '', $extra); + $logs = get_logs('ratelimited', $extra); + } + else { + $logs = get_logs('ratelimited'); + } + echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}'; + break; case "netfilter": // 0 is first record, so empty is fine if (isset($extra)) { @@ -662,6 +723,31 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u break; } break; + case "tls-policy-map": + switch ($object) { + case "all": + $tls_policy_maps_items = tls_policy_maps('get'); + if (!empty($tls_policy_maps_items)) { + foreach ($tls_policy_maps_items as $tls_policy_maps_item) { + if ($details = tls_policy_maps('details', $tls_policy_maps_item)) { + $data[] = $details; + } + else { + continue; + } + } + } + process_get_return($data); + break; + default: + $data = tls_policy_maps('details', $object); + if (!empty($data)) { + $data[] = $details; + } + process_get_return($data); + break; + } + break; case "policy_wl_mailbox": switch ($object) { default: @@ -829,6 +915,31 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u break; } break; + case "admin": + switch ($object) { + case "all": + $admins = admin('get'); + if (!empty($admins)) { + foreach ($admins as $admin) { + if ($details = admin('details', $admin)) { + $data[] = $details; + } + else { + continue; + } + } + process_get_return($data); + } + else { + echo '{}'; + } + break; + + default: + process_get_return(admin('details', $object)); + break; + } + break; case "u2f-registration": header('Content-Type: application/javascript'); if (($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && $_SESSION["mailcow_cc_username"] == $object) { @@ -865,6 +976,14 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u return; } break; + case "dkim": + switch ($object) { + default: + $data = dkim('details', $object); + process_get_return($data); + break; + } + break; default: echo '{}'; break; @@ -901,6 +1020,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "relayhost": process_delete_return(relayhost('delete', array('id' => $items))); break; + case "transport": + process_delete_return(transport('delete', array('id' => $items))); + break; case "rsetting": process_delete_return(rsettings('delete', array('id' => $items))); break; @@ -910,6 +1032,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "filter": process_delete_return(mailbox('delete', 'filter', array('id' => $items))); break; + case "mailq": + process_delete_return(mailq('delete', array('qid' => $items))); + break; case "qitem": process_delete_return(quarantine('delete', array('id' => $items))); break; @@ -919,6 +1044,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "recipient_map": process_delete_return(recipient_map('delete', array('id' => $items))); break; + case "tls-policy-map": + process_delete_return(tls_policy_maps('delete', array('id' => $items))); + break; case "fwdhost": process_delete_return(fwdhost('delete', array('forwardinghost' => $items))); break; @@ -950,9 +1078,18 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "eas_cache": process_delete_return(mailbox('delete', 'eas_cache', array('username' => $items))); break; + case "sogo_profile": + process_delete_return(mailbox('delete', 'sogo_profile', array('username' => $items))); + break; case "domain-admin": process_delete_return(domain_admin('delete', array('username' => $items))); break; + case "admin": + process_delete_return(admin('delete', array('username' => $items))); + break; + case "rlhash": + echo ratelimit('delete', null, implode($items)); + break; } break; case "edit": @@ -991,6 +1128,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "recipient_map": process_edit_return(recipient_map('edit', array_merge(array('id' => $items), $attr))); break; + case "tls-policy-map": + process_edit_return(tls_policy_maps('edit', array_merge(array('id' => $items), $attr))); + break; case "alias": process_edit_return(mailbox('edit', 'alias', array_merge(array('id' => $items), $attr))); break; @@ -1000,6 +1140,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "relayhost": process_edit_return(relayhost('edit', array_merge(array('id' => $items), $attr))); break; + case "transport": + process_edit_return(transport('edit', array_merge(array('id' => $items), $attr))); + break; case "rsetting": process_edit_return(rsettings('edit', array_merge(array('id' => $items), $attr))); break; @@ -1009,12 +1152,21 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "tls_policy": process_edit_return(mailbox('edit', 'tls_policy', array_merge(array('username' => $items), $attr))); break; + case "quarantine_notification": + process_edit_return(mailbox('edit', 'quarantine_notification', array_merge(array('username' => $items), $attr))); + break; case "qitem": process_edit_return(quarantine('edit', array_merge(array('id' => $items), $attr))); break; case "quarantine": process_edit_return(quarantine('edit', $attr)); break; + case "quota_notification": + process_edit_return(quota_notification('edit', $attr)); + break; + case "mailq": + process_edit_return(mailq('edit', array_merge(array('qid' => $items), $attr))); + break; case "time_limited_alias": process_edit_return(mailbox('edit', 'time_limited_alias', array_merge(array('address' => $items), $attr))); break; @@ -1039,6 +1191,12 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "rl-mbox": process_edit_return(ratelimit('edit', 'mailbox', array_merge(array('object' => $items), $attr))); break; + case "user-acl": + process_edit_return(acl('edit', 'user', array_merge(array('username' => $items), $attr))); + break; + case "da-acl": + process_edit_return(acl('edit', 'domainadmin', array_merge(array('username' => $items), $attr))); + break; case "alias-domain": process_edit_return(mailbox('edit', 'alias_domain', array_merge(array('alias_domain' => $items), $attr))); break; @@ -1048,6 +1206,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "domain-admin": process_edit_return(domain_admin('edit', array_merge(array('username' => $items), $attr))); break; + case "admin": + process_edit_return(admin('edit', array_merge(array('username' => $items), $attr))); + break; case "fwdhost": process_edit_return(fwdhost('edit', array_merge(array('fwdhost' => $items), $attr))); break; @@ -1064,9 +1225,6 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u elseif ($_SESSION['mailcow_cc_role'] == "user") { process_edit_return(edit_user_account($attr)); } - elseif ($_SESSION['mailcow_cc_role'] == "admin") { - process_edit_return(edit_admin_account($attr)); - } break; } break; diff --git a/data/web/lang/lang.ca.php b/data/web/lang/lang.ca.php index 32a10dad..91072ecf 100644 --- a/data/web/lang/lang.ca.php +++ b/data/web/lang/lang.ca.php @@ -204,8 +204,6 @@ $lang['mailbox']['target_address'] = 'Direcciรณ Goto'; $lang['mailbox']['username'] = "Nom d'usuari"; $lang['mailbox']['fname'] = 'Nom complert'; $lang['mailbox']['filter_table'] = 'Filtrar taula'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'En รบs (%)'; $lang['mailbox']['msg_num'] = 'Missatge #'; $lang['mailbox']['remove'] = 'Esborrar'; @@ -406,8 +404,6 @@ $lang['admin']['save'] = 'Desar els canvis'; $lang['admin']['admin'] = 'Administrador'; $lang['admin']['admin_details'] = "Editar detalls de l'administrador"; $lang['admin']['unchanged_if_empty'] = "Si no hi ha canvis, deixa'l en blanc"; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'Accรฉs'; $lang['admin']['no_record'] = 'Cap registre'; $lang['admin']['filter_table'] = 'Filtrar taula'; diff --git a/data/web/lang/lang.cs.php b/data/web/lang/lang.cs.php new file mode 100644 index 00000000..ca2a4684 --- /dev/null +++ b/data/web/lang/lang.cs.php @@ -0,0 +1,799 @@ +<?php +/* + * Czech language file for mailcow + * + * Author: radek@fastlinux.eu (www.fastlinux.eu) + * + */ + +$lang['header']['apps'] = 'Aplikace'; +$lang['footer']['loading'] = "Prosรญm ฤekejte..."; +$lang['header']['restart_sogo'] = 'Restartovat SOGo'; +$lang['header']['restart_netfilter'] = 'Restartovat netfilter'; +$lang['footer']['restart_container'] = 'Restartovat kontejner'; +$lang['footer']['restart_now'] = 'Restartovat nynรญ'; +$lang['footer']['restarting_container'] = 'Restartuje se kontejner, mลฏลพe to chvilku trvat...'; +$lang['footer']['restart_container_info'] = '<b>Dลฏleลพitรฉ:</b> Mลฏลพe trvat dlouho neลพ bude "Graceful restart" dokonฤen, prosรญm ฤekejte...'; + +$lang['footer']['confirm_delete'] = 'Potvrzenรญ smazรกnรญ'; +$lang['footer']['delete_these_items'] = 'Prosรญm potvrฤte vaลกe zmฤny objektu id:'; +$lang['footer']['delete_now'] = 'Smazat'; +$lang['footer']['cancel'] = 'Zruลกit'; + +$lang['footer']['hibp_nok'] = 'Nalezeno! Toto je potenciรกlnฤ nebezpeฤnรฉ heslo!'; +$lang['footer']['hibp_ok'] = 'Nebyla nalezena ลพรกdnรก shoda.'; + +$lang['danger']['mysql_error'] = "Chyba MySQL: %s"; +$lang['danger']['redis_error'] = "Chyba Redis: %s"; +$lang['danger']['unknown_tfa_method'] = "Neznรกmรก TFA metoda"; +$lang['danger']['totp_verification_failed'] = "TOTP ovฤลenรญ selhalo"; +$lang['success']['verified_totp_login'] = "TOTP pลihlรกลกenรญ ovฤลeno"; +$lang['danger']['u2f_verification_failed'] = "U2F ovฤลenรญ selhalo: %s"; +$lang['success']['verified_u2f_login'] = "U2F pลihlรกลกenรญ ovฤลeno"; +$lang['success']['verified_yotp_login'] = "Yubico OTP pลihlรกลกenรญ ovฤลeno"; +$lang['danger']['yotp_verification_failed'] = "Yubico OTP ovฤลenรญ selhalo: %s"; +$lang['danger']['ip_list_empty'] = "Seznam povolenรฝch IP nesmรญ bรฝt prรกzdnรฝ"; +$lang['danger']['invalid_destination'] = "Formรกt cรญle je ลกpatnรฝ"; +$lang['danger']['invalid_nexthop'] = "Formรกt skoku (Next hop) je ลกpatnรฝ"; +$lang['danger']['invalid_nexthop_authenticated'] = "Skok (Next hop) jiลพ existuje s rozรญlnรฝm pลihlaลกovacรญm รบdajem, nejdลรญve prosรญm aktualizujte existujรญcรญ pลihlaลกovacรญ รบdaje tohoto skoku."; +$lang['danger']['next_hop_interferes'] = "%s koliduje se skokem %s"; +$lang['danger']['next_hop_interferes_any'] = "Existujรญcรญ skok koliduje s %s"; +$lang['danger']['rspamd_ui_pw_length'] = "Heslo pro Rspamd UI musรญ bรฝt minimรกlnฤ 6 znakลฏ dlouhรฉ"; +$lang['success']['rspamd_ui_pw_set'] = "Heslo k Rspamd UI nastaveno"; +$lang['success']['queue_command_success'] = "Pลรญkaz pro frontu รบspฤลกnฤ dokonฤen"; +$lang['danger']['unknown'] = "Doลกlo k neznรกmรฉ chybฤ"; +$lang['danger']['malformed_username'] = "Vadnรฉ jmรฉno uลพivatele"; +$lang['info']['awaiting_tfa_confirmation'] = "ฤekรก se na potvrzenรญ TFA"; +$lang['success']['logged_in_as'] = "Pลihlรกลกen jako %s"; +$lang['danger']['login_failed'] = "Pลihlรกลกenรญ selhalo"; +$lang['danger']['set_acl_failed'] = "Chyba pลi nastavenรญ ACL"; +$lang['danger']['no_user_defined'] = "ลฝรกdnรฝ uลพivatel nenรญ definovรกn"; +$lang['danger']['script_empty'] = "Skript nesmรญ bรฝt prรกzdnรฝ"; +$lang['danger']['sieve_error'] = "Chyba Sieve parseru: %s"; +$lang['danger']['value_missing'] = "Prosรญm, uveฤte vลกechny hodnoty"; +$lang['danger']['filter_type'] = "ล patnรฝ typ filtru"; +$lang['danger']['domain_cannot_match_hostname'] = "Domรฉna a hostname nesmรญ bรฝt stejnรฉ"; +$lang['warning']['domain_added_sogo_failed'] = "Domรฉna byla pลidรกna ale selhal restart SOGo kontejneru, prosรญm zkontrolujte logy serveru."; +$lang['danger']['rl_timeframe'] = "Omezenรญ ฤasovรฉho rรกmce (Rate limit time frame) je ลกpatnรฉ"; +$lang['success']['rl_saved'] = "Omezenรญ provozu (Rate limit) pro objekt %s uloลพeno"; +$lang['success']['acl_saved'] = "ACL pro objekt %s uloลพeno"; +$lang['success']['deleted_syncjobs'] = "Smazanรฉ synchromizaฤnรญ รบlohy: %s"; +$lang['success']['deleted_syncjob'] = "Smazanรก synchronizaฤnรญ รบloha ID %s"; +$lang['success']['delete_filters'] = "Smazanรฉ filtry: %s"; +$lang['success']['delete_filter'] = "Smazanรฉ filtry ID %s"; +$lang['danger']['invalid_bcc_map_type'] = "ล patnรฝ typ BCC mapovรกnรญ"; +$lang['danger']['bcc_empty'] = "BCC cรญl nesmรญ bรฝt prรกzdnรฝ"; +$lang['danger']['bcc_must_be_email'] = "BCC mapovรกnรญ %s nenรญ sprรกvnรก email adresa"; +$lang['danger']['bcc_exists'] = "BCC mapovรกnรญ %s jiลพ existuje pro typ %s"; +$lang['success']['bcc_saved'] = "Poloลพka BCC mapovรกnรญ uloลพena"; +$lang['success']['bcc_edited'] = "Poloลพka BCC mapovรกnรญ %s upravena"; +$lang['success']['bcc_deleted'] = "Smazanรฉ poloลพky BCC mapovรกnรญ: %s"; +$lang['danger']['private_key_error'] = "Chyba soukromรฉhop klรญฤe: %s"; +$lang['danger']['map_content_empty'] = "Obsah mapovรกnรญ nesmรญ bรฝt prรกzdnรฝ"; +$lang['success']['settings_map_added'] = "Pลidรกna poloลพka mapovรกnรญ nastavenรญ"; +$lang['danger']['settings_map_invalid'] = "Poloลพka mapovรกnรญ nastavenรญ ID %s je ลกpatnรก"; +$lang['danger']['settings_map_removed'] = "Poloลพka mapovรกnรญ nastavenรญ: %s byla smazรกna"; +$lang['danger']['invalid_host'] = "Zadรกn neplatnรฝ klient (Host): %s"; +$lang['danger']['relayhost_invalid'] = "Nadลazenรฝ SMTP server (Relayhost) %s je ลกpatnรฝ"; +$lang['success']['saved_settings'] = "Nastavenรญ uloลพena"; +$lang['success']['db_init_complete'] = "Inicializace databรกze byla dokonฤena"; + +$lang['warning']['session_ua'] = "Token formulรกลe nenรญ platnรฝ: User-Agent validation error"; +$lang['warning']['session_token'] = "Token formulรกลe nenรญ platnรฝ: Token mismatch"; + +$lang['danger']['dkim_domain_or_sel_invalid'] = "DKIM nebo selektor domรฉnu je ลกpatnรฝ: %s"; +$lang['success']['dkim_removed'] = "DKIM klรญฤ %s byl odebrรกn"; +$lang['success']['dkim_added'] = "DKIM klรญฤ %s byl uloลพen"; +$lang['success']['dkim_duplicated'] = "DKIM klรญฤ domรฉny %s byl zkopรญrovรกn do %s"; +$lang['danger']['access_denied'] = "Pลรญstup byl odepลen nebo jsou neplatnรฉ data ve formulรกลi"; +$lang['danger']['domain_invalid'] = "Nรกzev domรฉny je prรกzdnรฝ nebo ลกpatnรฝ"; +$lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Max. kvรณta pลektoฤila limit pro domรฉnu"; +$lang['danger']['object_is_not_numeric'] = "Hodnota %s nenรญ numerickรก"; +$lang['success']['domain_added'] = "Pลidรกna domรฉna %s"; +$lang['success']['items_deleted'] = "Poloลพka %s byla รบspฤลกnฤ smazรกna"; +$lang['success']['item_deleted'] = "Poloลพka %s byla รบspฤลกnฤ smazรกna"; +$lang['danger']['alias_empty'] = "Adresa aliasu nesmรญ bรฝt prรกzdnรก"; +$lang['danger']['last_key'] = 'Nelze smazat poslednรญ klรญฤ'; +$lang['danger']['goto_empty'] = "Cรญlovรก adresa nesmรญ bรฝt prรกzdnรก"; +$lang['danger']['policy_list_from_exists'] = "Zรกznam s danรฝm jmรฉnem ji6 existuje"; +$lang['danger']['policy_list_from_invalid'] = "Zรกznam mรก ลกpatnรฝ formรกt"; +$lang['danger']['alias_invalid'] = "Adresa aliasu %s je ลกpatnรก"; +$lang['danger']['goto_invalid'] = "Cรญlovรก adresa %s nenรญ sprรกvnรก"; +$lang['danger']['alias_domain_invalid'] = "Domรฉnovรฝ alias %s nenรญ sprรกvnรฝ"; +$lang['danger']['target_domain_invalid'] = "Cรญlovรก domรฉna %s nenรญ sprรกvnรก"; +$lang['danger']['object_exists'] = "Objekt %s jiลพ existuje"; +$lang['danger']['domain_exists'] = "Domรฉna %s jiลพ existuje"; +$lang['danger']['alias_goto_identical'] = "Alias a cรญlovรก adresa nesmรญ bรฝt stejnรก"; +$lang['danger']['aliasd_targetd_identical'] = "Domรฉnovรฝ alias nesmรญ bรฝt stejnรฝ jako cรญlovรก domรฉna: %s"; +$lang['danger']['maxquota_empty'] = 'Max. kvรณta poลกtovnรญ schrรกnky nesmรญ bรฝt 0.'; +$lang['success']['alias_added'] = "Byl pลidรกn alias %s"; +$lang['success']['alias_modified'] = "Zmฤny aliasu %s byly uloลพeny"; +$lang['success']['mailbox_modified'] = "Zmฤny poลกtovnรญ schrรกnky %s byly uloลพeny"; +$lang['success']['resource_modified'] = "Zmฤny poลกtovnรญ schrรกnky %s byly uloลพeny"; +$lang['success']['object_modified'] = "Zmฤny objektu %s byly uloลพeny"; +$lang['success']['f2b_modified'] = "Zmฤny Fail2ban parametrลฏ byly uloลพeny"; +$lang['danger']['targetd_not_found'] = "Cรญlovรก domรฉna %s nenalezena"; +$lang['success']['aliasd_added'] = "Pลidรกn domรฉnovรฝ alias %s"; +$lang['success']['aliasd_modified'] = "Zmฤny domรฉnovรฉho aliau %s byly uloลพeny"; +$lang['success']['domain_modified'] = "Zmฤny domรฉny %s byly uloลพeny"; +$lang['success']['domain_admin_modified'] = "Zmฤny domรฉnovรฉho administrรกtora %s byly uloลพeny"; +$lang['success']['domain_admin_added'] = "Domรฉnovรฝ administrรกtor %s byl pลidรกn"; +$lang['success']['admin_added'] = "Administrรกtor %s byl pลidรกn"; +$lang['success']['admin_modified'] = "Zmฤny administrรกtora byly uloลพeny"; +$lang['success']['admin_api_modified'] = "Zmฤna API byla uloลพena"; +$lang['danger']['username_invalid'] = "Jmรฉno uลพivatele %s nemลฏลพe bรฝt pouลพito"; +$lang['danger']['password_mismatch'] = "Potvrzenรญ hesla nesouhlasรญ"; +$lang['danger']['password_complexity'] = "Heslo nesplลuje pravidla"; +$lang['danger']['password_empty'] = "Heslo nesmรญ bรฝt prรกzdnรฉ"; +$lang['danger']['login_failed'] = "Pลihlรกลกenรญ selhalo"; +$lang['danger']['mailbox_invalid'] = "Nรกzev poลกtovnรญ chrรกnky nenรญ sprรกvnรฝ"; +$lang['danger']['description_invalid'] = 'Popis zdroje %s je ลกpatnรฝ'; +$lang['danger']['resource_invalid'] = "Nรกzev zdroje %s je ลกpatnรฝ"; +$lang['danger']['is_alias'] = "%s je jiลพ znรกma jako adresa aliasu"; +$lang['danger']['is_alias_or_mailbox'] = "%s je jiลพ znรกma jako adresa aliasu, poลกtovnรญ schrรกnky nebo aliasu zdฤdฤnรฉho z domรฉnovรฉho aliasu."; +$lang['danger']['is_spam_alias'] = "%s je jiลพ znรกma jako adresa spam aliasu"; +$lang['danger']['quota_not_0_not_numeric'] = "Kvรณta musรญ bรฝt ฤรญslo >= 0"; +$lang['danger']['domain_not_found'] = 'Domรฉna %s nebyla nalezena'; +$lang['danger']['max_mailbox_exceeded'] = "Max. poฤet poลกtovnรญch schrรกnek pลekroฤen (%d z %d)"; +$lang['danger']['max_alias_exceeded'] = 'Pลekroฤeno max. mnoลพstvรญ aliasลฏ'; +$lang['danger']['mailbox_quota_exceeded'] = "Kvรณta pลekroฤila limit domรฉny (max. %d MiB)"; +$lang['danger']['mailbox_quota_left_exceeded'] = "Nenรญ dostatek volnรฉho mรญsta (zbรฝvรก: %d MiB)"; +$lang['success']['mailbox_added'] = "Poลกtovnรญ schrรกnka %s byla pลidรกna"; +$lang['success']['resource_added'] = "Zdroj %s byl pลidรกn"; +$lang['success']['domain_removed'] = "Domรฉna %s byla odebrรกna"; +$lang['success']['alias_removed'] = "Alias %s byl odebrรกn"; +$lang['success']['alias_domain_removed'] = "Domรฉnovรฝ alias %s byl odebrรกn"; +$lang['success']['domain_admin_removed'] = "Domรฉnovรฝ administrรกtor %s byl odebrรกn"; +$lang['success']['admin_removed'] = "Administrรกtor %s byl odebrรกn"; +$lang['success']['mailbox_removed'] = "Poลกtovnรญ schrรกnka %s byla odebrรกna"; +$lang['success']['eas_reset'] = "ActiveSync zaลรญzenรญ uลพivatele %s bylo vyresetovรกno"; +$lang['success']['sogo_profile_reset'] = "SOGo profil uลพivatele %s byl resetovรกn"; +$lang['success']['resource_removed'] = "Zdroj %s byl odebrรกn"; +$lang['warning']['cannot_delete_self'] = "Nelze smazat prรกvฤ pลihlรกลกenรฉho uลพivatele"; +$lang['warning']['no_active_admin'] = "Nelze deaktivovat poslednรญho aktivnรญho administrรกtora"; +$lang['danger']['max_quota_in_use'] = "Kvรณta poลกtovnรญ schrรกnky musรญ bรฝt vฤtลกรญ nebo rovna %d MiB"; +$lang['danger']['domain_quota_m_in_use'] = "Kvรณta domรฉny muรญ bรฝt vฤtลกรญ nebo rovna %s MiB"; +$lang['danger']['mailboxes_in_use'] = "Max. poฤet poลกtovnรญch schrรกnek musรญ bรฝt vฤtลกรญ nebo rovno %d"; +$lang['danger']['aliases_in_use'] = "Max. poฤet aliasลฏ musรญ bรฝt vฤtลกรญ nebo rovno %d"; +$lang['danger']['sender_acl_invalid'] = "ACL hodnota odesรญlatele %s je ลกpatnรก"; +$lang['danger']['domain_not_empty'] = "Nelze odebrat domรฉnu, kterรก nenรญ prรกzdnรก"; +$lang['danger']['validity_missing'] = 'Pลidejte dobu platnosti'; +$lang['user']['loading'] = "Naฤรญtรก se..."; +$lang['user']['force_pw_update'] = 'Pro pลรญstup k groupware funkcรญm <b>musรญte zmฤnit heslo</b>.'; +$lang['user']['active_sieve'] = "Aktivnรญ filtr"; +$lang['user']['show_sieve_filters'] = "Zobrazit aktivnรญ sieve filtr uลพivatele"; +$lang['user']['no_active_filter'] = "Nenรญ k dispozici ลพรกdnรฝ aktivnรญ filtr"; +$lang['user']['messages'] = "zprรกv"; // "123 messages" +$lang['user']['in_use'] = "Obsazeno"; +$lang['user']['user_change_fn'] = ""; +$lang['user']['user_settings'] = 'Uลพivatelskรฉ nastavenรญ'; +$lang['user']['mailbox_details'] = 'Podrobnosti poลกtovnรญ schrรกnky'; +$lang['user']['change_password'] = 'Zmฤnit heslo'; +$lang['user']['client_configuration'] = 'Zobrazit prลฏvodce konfiguracรญ pro e-mailovรฉ klienty a smartphony'; +$lang['user']['new_password'] = 'Novรฉ heslo'; +$lang['user']['save_changes'] = 'Uloลพit zmฤny'; +$lang['user']['password_now'] = 'Souฤasnรฉ heslo (pro schvรกlenรญ zmฤny)'; +$lang['user']['new_password_repeat'] = 'Potvrzenรญ novรฉho hesla (opakujte)'; +$lang['user']['new_password_description'] = 'Poลพadavek: dรฉlka min. 6 znakลฏ, pรญsmena a ฤรญsla.'; +$lang['user']['spam_aliases'] = 'Doฤasnรฉ e-mailovรฉ aliasy'; +$lang['user']['alias'] = 'Alias'; +$lang['user']['shared_aliases'] = 'Sdรญlenรฉ aliasy'; +$lang['user']['shared_aliases_desc'] = 'Sdรญlenรฉ aliasy nejsou ovlivnฤny uลพivatelskรฝm nastavenรญm, jako je spam filtr nebo pravidla ลกifrovรกnรญ. Odpovรญdajรญcรญ nastavenรญ mohou provรกdฤt pouze sprรกvci na รบrovni domรฉny.'; +$lang['user']['direct_aliases'] = 'Pลรญmรฉ aliasy'; +$lang['user']['direct_aliases_desc'] = 'Pลรญmรฉ aliasy jsou ovlivnฤny filtrem spamu a nastavenรญm pravidel TLS'; +$lang['user']['is_catch_all'] = 'Catch-all pro domรฉnu/y'; +$lang['user']['aliases_also_send_as'] = 'Takรฉ smรญ odesรญlat jako uลพivatel'; +$lang['user']['aliases_send_as_all'] = 'Nekontroluje se pลรญstup odesรญlatele pro nรกsledujรญcรญ domรฉnu(y) a jejich alias domรฉny:'; +$lang['user']['alias_create_random'] = 'Generovat nรกhodnรฝ alias'; +$lang['user']['alias_extend_all'] = 'Prodlouลพit aliasy o 1 hodinu'; +$lang['user']['alias_valid_until'] = 'Platnรฝ do'; +$lang['user']['alias_remove_all'] = 'Odstranit vลกechny aliasy'; +$lang['user']['alias_time_left'] = 'Zbรฝvajรญcรญ ฤas'; +$lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['alias_select_validity'] = 'Doba platnosti'; +$lang['user']['sync_jobs'] = 'Synchronizaฤnรญ รบlohy'; +$lang['user']['expire_in'] = 'Vyprลกรญ za'; +$lang['user']['hour'] = 'hodinu'; +$lang['user']['hours'] = 'hodin'; +$lang['user']['day'] = 'den'; +$lang['user']['week'] = 'tรฝden'; +$lang['user']['weeks'] = 'tรฝdny'; +$lang['user']['spamfilter'] = 'Spam filtr'; +$lang['admin']['spamfilter'] = 'Spam filtr'; +$lang['user']['spamfilter_wl'] = 'Seznam povolenรฝch (Whitelist)'; +$lang['user']['spamfilter_wl_desc'] = 'E-mailovรฉ adresy v seznamu povolenรฝch (Whitelist) <b>nebudou nikdy klasifikovanรฉ jako spam</b>. Mohou bรฝt pouลพity zรกstupnรฉ znaky (*). Filtr je aplikovรกn pouze na pลรญmรฉ aliasy (aliasy s jednou cรญlovou poลกtovnรญ schrรกnkou), s vรฝjimkou aliasลฏ typu catch-all a samotnรฉ poลกtovnรญ schrรกnky.'; +$lang['user']['spamfilter_bl'] = 'Seznam zakรกzanรฝch (Blacklist)'; +$lang['user']['spamfilter_bl_desc'] = 'E-mailovรฉ adresy v seznamu zakรกzanรฝch (Blacklist) <b>budou vลพdy klasifikovanรฉ jako spam a odmรญtnutรฉ</b>. Mohou bรฝt pouลพity zรกstupnรฉ znaky(*). Filtr je aplikovรกn pouze na pลรญmรฉ aliasy (aliasy s jednou cรญlovou poลกtovnรญ schrรกnkou), s vรฝjimkou aliasลฏ typu catch-all a samotnรฉ poลกtovnรญ schrรกnky.'; +$lang['user']['spamfilter_behavior'] = 'Hodnocenรญ'; +$lang['user']['spamfilter_table_rule'] = 'Pravidlo'; +$lang['user']['spamfilter_table_action'] = 'Akce'; +$lang['user']['spamfilter_table_empty'] = 'ลฝรกdnรก data k zobrazenรญ'; +$lang['user']['spamfilter_table_remove'] = 'smazat'; +$lang['user']['spamfilter_table_add'] = 'Pลidat poloลพku'; +$lang['user']['spamfilter_green'] = 'Zelenรก: tato zprรกva nenรญ spam'; +$lang['user']['spamfilter_yellow'] = 'ลฝlutรก: tato zprรกva mลฏลพe bรฝt spam, bude oznaฤena jako spam a pลesunuta do sloลพky nevyลพรกdanรก poลกta'; +$lang['user']['spamfilter_red'] = 'ฤervenรก: Tato zprรกva je spam a server ji odmรญtne'; +$lang['user']['spamfilter_default_score'] = 'Vรฝchozรญ hodnoty:'; +$lang['user']['spamfilter_hint'] = 'Prvnรญ hodnota pลedstavuje "nรญzkรฉ spam skรณre" a druhรก "vysokรฉ spam skรณre".'; +$lang['user']['spamfilter_table_domain_policy'] = "n/a (domรฉnovรก politika)"; +$lang['user']['waiting'] = "ฤekรกnรญ"; +$lang['user']['status'] = "Stav"; +$lang['user']['running'] = "Bฤลพรญ"; + +$lang['user']['tls_policy_warning'] = '<strong>Upozornฤnรญ:</strong> Pokud se rozhodnete vynutit ลกifrovanรฝ pลenos poลกty, mลฏลพe dojรญt ke ztrรกtฤ e-mailลฏ.<br>Zprรกvy, kterรฉ nesplลujรญ tuto politiku, budou poลกtovnรญm systรฉmem odmรญtnuty.<br>Tato volba se vztahuje k vaลกรญ primรกrnรญ e-mailovรฉ adrese (pลihlaลกovacรญ jmรฉno), vลกem adresรกm odvozenรฝch z domรฉnovรฝch aliasลฏ i aliasลฏ majรญcรญch tuto poลกtovnรญ chrรกnku jako cรญl.'; +$lang['user']['tls_policy'] = 'Politika ลกifrovรกnรญ'; +$lang['user']['tls_enforce_in'] = 'Vynutit TLS pro pลรญchozรญ poลกtu '; +$lang['user']['tls_enforce_out'] = 'Vynutit TLS pro odchozรญ poลกtu'; +$lang['user']['no_record'] = 'ลฝรกdnรฝ zรกznam'; + + +$lang['user']['tag_handling'] = 'Chovรกnรญ oznaฤenรฉ (tagged) poลกty'; +$lang['user']['tag_in_subfolder'] = 'V podsloลพce'; +$lang['user']['tag_in_subject'] = 'V pลedmฤtu'; +$lang['user']['tag_in_none'] = 'Nedฤlat nic'; +$lang['user']['tag_help_explain'] = 'V podsloลพce: v INBOXU bude vytvoลena novรก podsloลพka pojmenovanรก po oznaฤenรญ (tagu) zprรกvy ("INBOX / Facebook").<br> +V pลedmฤtu: nรกzev znaฤky (tagu) bude pลidรกvรกn k pลedmฤtu mailu, napลรญklad: "[Facebook] Moje zprรกvy".'; +$lang['user']['tag_help_example'] = 'Pลรญklad e-mailovรฉ adresy s oznaฤenรญm (tagem): me<b>+Facebook</b>@example.org'; + +$lang['user']['eas_reset'] = 'Smazat mezipamฤลฅ (cache) zaลรญzenรญ ActiveSync'; +$lang['user']['eas_reset_now'] = 'Smazat'; +$lang['user']['eas_reset_help'] = 'Obnovenรญ mezipamฤti zaลรญzenรญ pomลฏลพe obnovit poลกkozenรฝ profil sluลพby ActiveSync.<br><b>Upozornฤnรญ:</b> Vลกechna data budou opฤtovnฤ staลพena!'; + +$lang['user']['sogo_profile_reset'] = 'Resetovat profil SOGo'; +$lang['user']['sogo_profile_reset_now'] = 'Resetovat profil'; +$lang['user']['sogo_profile_reset_help'] = 'Tato volba odstranรญ uลพivatelskรฝ profil SOGo a <b>nenรกvratnฤ vymaลพe vลกechna data</b>.'; + +$lang['user']['encryption'] = 'ล ifrovรกnรญ'; +$lang['user']['username'] = 'Jmรฉno uลพivatele'; +$lang['user']['last_run'] = 'Naposledy spuลกtฤno'; +$lang['user']['excludes'] = 'Vylouฤenรฉ'; +$lang['user']['interval'] = 'Interval'; +$lang['user']['active'] = 'Aktivnรญ'; +$lang['user']['action'] = 'Akce'; +$lang['user']['edit'] = 'Upravit'; +$lang['user']['remove'] = 'Smazat'; +$lang['user']['create_syncjob'] = 'Vytvoลit novou synchronizaฤnรญ รบlohu'; + +$lang['start']['mailcow_apps_detail'] = 'Pouลพijte mailcow aplikace pro pลรญstup k vaลกim e-mailลฏm, kalendรกลi, kontaktลฏm a dalลกรญm funkcรญm.'; +$lang['start']['mailcow_panel_detail'] = '<b>Administrรกtoลi domรฉn</b> mohou vytvรกลet, upravovat nebo mazat mailboxy a aliasy. Dรกle mohou upravovat parametry domรฉny a zobrazovat dalลกรญ informace o jejich pลidฤlenรฝch domรฉnรกch.<br> +<b>Uลพivatelรฉ</b> mohou vytvรกลet ฤasovฤ omezenรฉ aliasy (spam aliases), mฤnit jejich heslo a nastavovat spam filtr.'; +$lang['start']['imap_smtp_server_auth_info'] = 'Pouลพijte prosรญm vaลกรญ celou e-mailovou adresu a zpลฏsob ovฤลovรกnรญ PLAIN.<br> +Vaลกe pลihlaลกovacรญ รบdaje budou zaลกifrovรกny na stranฤ serveru.'; +$lang['start']['help'] = 'Zobrazit/Skrรฝt panel nรกpovฤdy'; +$lang['header']['mailcow_settings'] = 'Nastavenรญ'; +$lang['header']['administration'] = 'Hlavnรญ nastavenรญ'; +$lang['header']['mailboxes'] = 'Nastavenรญ poลกty'; +$lang['header']['user_settings'] = 'Uลพivatelskรก nastavenรญ'; +$lang['header']['quarantine'] = "Karantรฉna"; +$lang['header']['debug'] = "Systรฉmovรฉ informace"; +$lang['quarantine']['disabled_by_config'] = "Funkce karantรฉna je momentรกlnฤ zakรกzanรก."; +$lang['mailbox']['tls_policy_maps'] = 'Pลetฤลพovรกnรญ TLS pravidel'; +$lang['mailbox']['tls_policy_maps_long'] = 'Pลetฤลพovรกnรญ odchozรญch TLS pravidel (TLS policy map overrides).'; +$lang['mailbox']['tls_policy_maps_info'] = 'Tato mapa pravidel pลetฤลพuje odchozรญ transportnรญ TLS pravidla nezรกvisle na TLS nastavenรญ uลพivatele.<br> + Prosรญm prostudujte <a href="http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps" target="_blank">the "smtp_tls_policy_maps" dokumentaci</a> pro dalลกรญ informace.'; +$lang['mailbox']['tls_enforce_in'] = 'Vynucenรญ TLS pro pลรญchozรญ'; +$lang['mailbox']['tls_enforce_out'] = 'Vynucenรญ TLS pro odchozรญ'; +$lang['mailbox']['tls_map_dest'] = 'Cรญl'; +$lang['mailbox']['tls_map_dest_info'] = 'Pลรญklady: example.org, .example.org, mail@example.org, [mail.example.org]:25'; +$lang['mailbox']['tls_map_policy'] = 'Pravidlo'; +$lang['mailbox']['tls_map_parameters'] = 'Parametry'; +$lang['mailbox']['tls_map_parameters_info'] = 'Prรกzdnรฉ nebo parametry, napลรญklad: protocols=!SSLv2 ciphers=medium exclude=3DES'; +$lang['mailbox']['booking_0'] = 'Vลพdy zobrazovat jako volnรฝ'; +$lang['mailbox']['booking_lt0'] = 'Neomezenรฝ, ale pลi rezervaci je zaneprรกzdnฤn (booked)'; +$lang['mailbox']['booking_custom'] = 'Hard-limit na vlastnรญ poฤet rezervacรญ'; +$lang['mailbox']['booking_0_short'] = 'Vลพdy volnรฝ'; +$lang['mailbox']['booking_lt0_short'] = 'Soft limit'; +$lang['mailbox']['booking_custom_short'] = 'Hard limit'; +$lang['mailbox']['domain'] = 'Domรฉna'; +$lang['mailbox']['spam_aliases'] = 'Doฤasnรฝ alias'; +$lang['mailbox']['multiple_bookings'] = 'Vรญcenรกsobnรฉ rezervace'; +$lang['mailbox']['kind'] = 'Druh'; +$lang['mailbox']['description'] = 'Popis'; +$lang['mailbox']['alias'] = 'Alias'; +$lang['mailbox']['aliases'] = 'Aliasy'; +$lang['mailbox']['domains'] = 'Domรฉny'; +$lang['admin']['domain'] = 'Domรฉna'; +$lang['admin']['domain_s'] = 'Domรฉna/y'; +$lang['mailbox']['mailboxes'] = 'Poลกtovnรญ schrรกnky'; +$lang['mailbox']['resources'] = 'Zdroje'; +$lang['mailbox']['mailbox_quota'] = 'Max. velikost poลกtovnรญ schrรกnky'; +$lang['mailbox']['domain_quota'] = 'Kvรณta'; +$lang['mailbox']['active'] = 'Aktivnรญ'; +$lang['mailbox']['action'] = 'Akce'; +$lang['mailbox']['backup_mx'] = 'Zรกloลพnรญ MX'; +$lang['mailbox']['domain_aliases'] = 'Domรฉnovรฉ aliasy'; +$lang['mailbox']['target_domain'] = 'Cรญlovรก domรฉna'; +$lang['mailbox']['target_address'] = 'Cรญlovรก adresa'; +$lang['mailbox']['username'] = 'Jmรฉno uลพivatele'; +$lang['mailbox']['fname'] = 'Celรฉ jmรฉno'; +$lang['mailbox']['filter_table'] = 'Tabulka filtrลฏ'; +$lang['mailbox']['yes'] = '✓'; +$lang['mailbox']['no'] = '✕'; +$lang['mailbox']['in_use'] = 'Obsazeno (%)'; +$lang['mailbox']['msg_num'] = 'Zprรกva #'; +$lang['mailbox']['remove'] = 'Smazat'; +$lang['mailbox']['edit'] = 'Upravit'; +$lang['mailbox']['no_record'] = 'ลฝรกdnรฝ zรกznam pro objekt %s'; +$lang['mailbox']['no_record_single'] = 'ลฝรกdnรฝ zรกznam'; +$lang['mailbox']['add_domain'] = 'Pลidat domรฉnu'; +$lang['mailbox']['add_domain_alias'] = 'Pลidat domรฉnovรฝ alias'; +$lang['mailbox']['add_mailbox'] = 'Pลidat poลกtovnรญ schrรกnku'; +$lang['mailbox']['add_resource'] = 'Pลidat zdroj'; +$lang['mailbox']['add_alias'] = 'Pลidat alias'; +$lang['mailbox']['add_domain_record_first'] = 'Prosรญm vytvoลte nejdลรญve domรฉnu'; +$lang['mailbox']['empty'] = 'ลฝรกdnรฉ vรฝsledky'; +$lang['mailbox']['toggle_all'] = 'Oznaฤit vลกe'; +$lang['mailbox']['quick_actions'] = 'Akce'; +$lang['mailbox']['activate'] = 'Zapnout'; +$lang['mailbox']['deactivate'] = 'Vypnout'; +$lang['mailbox']['owner'] = 'Vlastnรญk'; +$lang['mailbox']['mins_interval'] = 'Interval (min)'; +$lang['mailbox']['last_run'] = 'Naposledy spuลกtฤno'; +$lang['mailbox']['excludes'] = 'Vylouฤenรฉ'; +$lang['mailbox']['last_run_reset'] = 'Plรกnovat dalลกรญ'; +$lang['mailbox']['sieve_info'] = 'Mลฏลพete uloลพit vรญce filtrลฏ pro kaลพdรฉho uลพivatele, ale souฤasnฤ mลฏลพe bรฝt aktivnรญ pouze jeden prefilter a jeden postfilter.<br> +Kaลพdรฝ filtr bude zpracovรกn v danรฉm poลadรญ. Ani chyba pลi vykonรกvรกnรญ skriptu nebo snaha o pozdrลพenรญ nezastavรญ vykonรกnรญ dalลกรญch skriptลฏ.<br> +Prefilter โ Uลพivatelskรฉ skripty โ Postfilter โ <a href="https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after" target="_blank">global sieve postfilter</a>'; +$lang['info']['no_action'] = 'Nenรญ pouลพitelnรก ลพรกdnรก akce'; + +$lang['edit']['syncjob'] = 'Upravit synchronizaฤnรญ รบlohu'; +$lang['edit']['client_id'] = 'ID klienta'; +$lang['edit']['client_secret'] = 'Client secret'; +$lang['edit']['scope'] = 'Rozsah (Scope)'; +$lang['edit']['grant_types'] = 'Grant types'; +$lang['edit']['redirect_uri'] = 'Redirect/Callback URL'; +$lang['edit']['hostname'] = 'Jmรฉno hostitele (Hostname)'; +$lang['edit']['encryption'] = 'ล ifrovรกnรญ'; +$lang['edit']['maxage'] = 'Maximรกlnรญ stรกลรญ zprรกv ve dnech, kterรฉ budou staลพeny ze vzdรกlenรฉho umรญstฤnรญ<br><small>(0 = ignorovat stรกลรญ)</small>'; +$lang['edit']['maxbytespersecond'] = 'Max. bajtลฏ za sekundu <br><small>(0 = neomezeno)</small>'; +$lang['edit']['automap'] = 'Pokusit se automaticky mapovat sloลพky ("Sent items", "Sent" => "Sent" etc.)'; +$lang['edit']['skipcrossduplicates'] = 'Pลeskoฤit duplicitnรญ zprรกvy mezi sloลพkami (first come, first serve)'; +$lang['add']['automap'] = 'Pokusit se automaticky mapovat sloลพky ("Sent items", "Sent" => "Sent" etc.)'; +$lang['add']['skipcrossduplicates'] = 'Pลeskoฤit duplicitnรญ zprรกvy mezi sloลพkami (first come, first serve)'; +$lang['edit']['subfolder2'] = 'Synchronizace do podsloลพky v cรญlovรฉm umรญstฤnรญ<br><small>(prรกzdnรฉ = nepouลพรญvat podsloลพku)</small>'; +$lang['edit']['mins_interval'] = 'Interval (min)'; +$lang['edit']['exclude'] = 'Vylouฤit objekty (regex)'; +$lang['edit']['save'] = 'Uloลพit zmฤny'; +$lang['edit']['username'] = 'Jmรฉno uลพivatele'; +$lang['edit']['max_mailboxes'] = 'Max. mnoลพstvรญ poลกtovnรญch schrรกnek'; +$lang['edit']['title'] = 'รprava objektu'; +$lang['edit']['target_address'] = 'Cรญlovรก adresa/y <small>(odฤlenรฉ ฤรกrkou)</small>'; +$lang['edit']['active'] = 'Aktivnรญ'; +$lang['edit']['force_pw_update'] = 'Vynucenรญ zmฤny hesla pลi pลรญลกtรญm pลihlรกลกenรญ'; +$lang['edit']['force_pw_update_info'] = 'Uลพivatel se bude moci pลihlรกsit pouze do administrace รบฤtu.'; +$lang['edit']['sogo_access'] = 'Udฤlit pลรญstup k Sogo'; +$lang['edit']['sogo_access_info'] = 'Toto nastavenรญ neovlivลuje pลรญstup k ostatnรญm sluลพbรกm, ani nezmฤnรญ existujรญcรญ profil uลพivatele SOGo.'; +$lang['edit']['target_domain'] = 'Cรญlovรก domรฉna'; +$lang['edit']['password'] = 'Heslo'; +$lang['edit']['password_repeat'] = 'Potvrzenรญ novรฉho hesla (opakujte)'; +$lang['edit']['domain_admin'] = 'Upravit domรฉnovรฉho administrรกtora'; +$lang['edit']['domain'] = 'รprava domรฉny'; +$lang['edit']['edit_alias_domain'] = 'Upravit domรฉnovรฝ alias'; +$lang['edit']['domains'] = 'Domรฉny'; +$lang['edit']['alias'] = 'Upravit alias'; +$lang['edit']['mailbox'] = 'รprava poลกtovnรญ schrรกnky'; +$lang['edit']['description'] = 'Popis'; +$lang['edit']['max_aliases'] = 'Max. mnoลพstvรญ aliasลฏ'; +$lang['edit']['max_quota'] = 'Max. kvรณta poลกtovnรญ schrรกnky (MiB)'; +$lang['edit']['domain_quota'] = 'Kvรณta domรฉny'; +$lang['edit']['backup_mx_options'] = 'Moลพnosti zรกloลพnรญho MX'; +$lang['edit']['relay_domain'] = 'Pลesmฤrovรกnรญ provozu (Relay) domรฉny'; +$lang['edit']['relay_all'] = 'Pลesmฤrovรกnรญ provozu (Relay) pro vลกechny pลรญjemce'; +$lang['edit']['relay_all_info'] = '<small>Pokud se rozhodnete <b>nepลesmฤrovat</b> provoz pro vลกechny pลรญjemce, bude nutnรฉ pลidat prรกzdnou ("blind") poลกtovnรญ schrรกnku pro kaลพdรฉho pลรญjemce, kterรฝ se mรก pลesmฤrovรกvat.</small>'; +$lang['edit']['full_name'] = 'Celรฉ jmรฉno'; +$lang['edit']['quota_mb'] = 'Kvรณta (MiB)'; +$lang['edit']['sender_acl'] = 'Povolit odeslรกnรญ jako'; +$lang['edit']['sender_acl_disabled'] = 'โณ <span class="label label-danger">Sender check is disabled</span>'; +$lang['user']['sender_acl_disabled'] = '<span class="label label-danger">Sender check is disabled</span>'; +$lang['edit']['previous'] = 'Pลedchozรญ strรกnka'; +$lang['edit']['unchanged_if_empty'] = 'Pokud se nemฤnรญ, ponechte prรกzdnรฉ'; +$lang['edit']['dont_check_sender_acl'] = "Disable sender check for domain %s (+ alias domains)"; +$lang['edit']['multiple_bookings'] = 'Vรญcenรกsobnรฉ rezervace'; +$lang['edit']['kind'] = 'Druh'; +$lang['edit']['resource'] = 'Zdroje'; +$lang['edit']['relayhost'] = 'Pลeposรญlรกnรญ zรกvislรฉ na odesรญlateli'; + +$lang['acl']['spam_alias'] = 'Doฤasnรฉ aliasy'; +$lang['acl']['tls_policy'] = 'Pravidla TLS'; +$lang['acl']['spam_score'] = 'Spam skรณre'; +$lang['acl']['spam_policy'] = 'Blacklist/Whitelist'; +$lang['acl']['delimiter_action'] = 'Delimiter akce'; +$lang['acl']['syncjobs'] = 'Synchronizaฤnรญ รบlohy'; +$lang['acl']['eas_reset'] = 'Resetovรกnรญ EAS zaลรญzenรญ'; +$lang['acl']['sogo_profile_reset'] = 'Resetovรกnรญ profilu SOGo'; +$lang['acl']['quarantine'] = 'Karantรฉna'; +$lang['acl']['login_as'] = 'Pลihlรกsit jako uลพivatel poลกtovnรญ schrรกnky'; +$lang['acl']['bcc_maps'] = 'BCC maps'; +$lang['acl']['filters'] = 'Filtry'; +$lang['acl']['ratelimit'] = 'Omezovรกnรญ provozu'; +$lang['acl']['recipient_maps'] = 'Recipient maps'; +$lang['acl']['prohibited'] = 'Zakรกzรกno z dลฏvodu ACL'; + +$lang['add']['generate'] = 'generovat'; +$lang['add']['syncjob'] = 'Pลidat synchronizaฤnรญ รบlohu'; +$lang['add']['syncjob_hint'] = 'Upozornฤnรญ: Heslo bude uloลพeno v prostรฉm textu!'; +$lang['add']['hostname'] = 'Jmรฉno hostitele (Host)'; +$lang['add']['destination'] = 'Cรญl'; +$lang['add']['nexthop'] = 'Dalลกรญ skok (Next hop)'; +$lang['edit']['nexthop'] = 'Dalลกรญ skok (Next hop)'; +$lang['add']['port'] = 'Port'; +$lang['add']['username'] = 'Jmรฉno uลพivatele'; +$lang['add']['enc_method'] = 'Metoda ลกifrovรกnรญ'; +$lang['add']['mins_interval'] = 'Interval dotazu (Polling interval) (minuty)'; +$lang['add']['exclude'] = 'Vylouฤit objekty (regex)'; +$lang['add']['delete2duplicates'] = 'Odstranit duplicity v cรญlovรฉm mรญstฤ'; +$lang['add']['delete1'] = 'Odstranit ze zdroje po dokonฤenรญ'; +$lang['add']['delete2'] = 'Smazat zprรกvy v cรญli, kterรฉ nejsou ve zdroji'; +$lang['add']['custom_params'] = 'Vlastnรญ parametry'; +$lang['add']['subscribeall'] = 'Odebรญrat vลกechny sloลพky'; +$lang['add']['timeout1'] = 'ฤasovรฝ limit pro pลipojenรญ ke vzdรกlenรฉmu hostiteli'; +$lang['add']['timeout2'] = 'ฤasovรฝ limit pro pลipojenรญ k lokรกlnรญmu hostiteli'; +$lang['edit']['timeout1'] = 'ฤasovรฝ limit pro pลipojenรญ ke vzdรกlenรฉmu hostiteli'; +$lang['edit']['timeout2'] = 'ฤasovรฝ limit pro pลipojenรญ k lokรกlnรญmu hostiteli'; + +$lang['edit']['delete2duplicates'] = 'Odstranit duplicity v cรญlovรฉm mรญstฤ'; +$lang['edit']['delete1'] = 'Odstranit ze zdroje po dokonฤenรญ'; +$lang['edit']['delete2'] = 'Smazat zprรกvy v cรญli, kterรฉ nejsou ve zdroji'; + +$lang['add']['domain_matches_hostname'] = 'Domรฉna %s se shoduje s hostname'; +$lang['add']['domain'] = 'Domรฉna'; +$lang['add']['active'] = 'Aktivnรญ'; +$lang['add']['multiple_bookings'] = 'Vรญcenรกsobnรฉ rezervace'; +$lang['add']['description'] = 'Popis'; +$lang['add']['max_aliases'] = 'Max. mnoลพstvรญ aliasลฏ'; +$lang['add']['max_mailboxes'] = 'Max. mnoลพstvรญ poลกtovnรญch schrรกnek'; +$lang['add']['mailbox_quota_m'] = 'Max. kvรณta poลกtovnรญ schrรกnky (MiB)'; +$lang['add']['domain_quota_m'] = 'Celkovรก kvรณta domรฉny (MiB)'; +$lang['add']['backup_mx_options'] = 'Moลพnosti zรกloลพnรญho MX'; +$lang['add']['relay_all'] = 'Pลesmฤrovรกnรญ provozy (Relay) pro vลกechny pลรญjemce'; +$lang['add']['relay_domain'] = 'Pลesmฤrovรกnรญ provozu domรฉny'; +$lang['add']['relay_all_info'] = '<small>Pokud se rozhodnete <b>nepลesmฤrovat</b> provoz pro vลกechny pลรญjemce, bude nutnรฉ pลidat prรกzdnou ("blind") poลกtovnรญ schrรกnku pro kaลพdรฉho pลรญjemce, kterรฝ se mรก pลesmฤrovรกvat.</small>'; +$lang['add']['alias_address'] = 'Adresa/y aliasลฏ'; +$lang['add']['alias_address_info'] = '<small>Kompletnรญ email adresa/y nebo @example.com pro zachycenรญ vลกech zprรกv pro domรฉnu (oddฤlenรฉ ฤรกrkami). <b>pouze mailcow domรฉny</b>.</small>'; +$lang['add']['alias_domain_info'] = '<small>Platnรฉ nรกzvy domรฉn (oddฤlenรฉ ฤรกrkami).</small>'; +$lang['add']['target_address'] = 'Cรญlovรฉ adresy'; +$lang['add']['target_address_info'] = '<small>Kompletnรญ email adresa/y (oddฤlenรฉ ฤรกrkami).</small>'; +$lang['add']['alias_domain'] = 'Domรฉnovรฝ alias'; +$lang['add']['select'] = 'Prosรญm vyberte...'; +$lang['add']['target_domain'] = 'Cรญlovรก domรฉna'; +$lang['add']['kind'] = 'Druh'; +$lang['add']['mailbox_username'] = 'Jmรฉno uลพivatele (levรก ฤรกst email adresy)'; +$lang['add']['full_name'] = 'Celรฉ jmรฉno'; +$lang['add']['quota_mb'] = 'Kvรณta (MiB)'; +$lang['add']['select_domain'] = 'Nejdลรญve vyberte domรฉnu'; +$lang['add']['password'] = 'Heslo'; +$lang['add']['password_repeat'] = 'Potvrzenรญ novรฉho hesla (opakujte)'; +$lang['add']['restart_sogo_hint'] = 'Po pลidรกnรญ novรฉ domรฉny je nutnรฉ restartovat SOGo kontejner!'; +$lang['add']['goto_null'] = 'Tiลกe odmรญtat poลกtu'; +$lang['add']['goto_ham'] = 'Uฤit se jako <span class="text-success"><b>ham</b></span>'; +$lang['add']['goto_spam'] = 'Uฤit se jako <span class="text-danger"><b>spam</b></span>'; +$lang['add']['validation_success'] = 'รspฤลกnฤ ovฤลeno'; +$lang['add']['activate_filter_warn'] = 'Pokud je zaลกkrtlรก volba "Aktivnรญ", budou vลกechny ostatnรญ filtry deaktivovรกny.'; +$lang['add']['validate'] = 'Ovฤลit'; +$lang['mailbox']['add_filter'] = 'Pลidat filtr'; +$lang['add']['sieve_desc'] = 'Krรกtkรฝ popis'; +$lang['edit']['sieve_desc'] = 'Krรกtkรฝ popis'; +$lang['add']['sieve_type'] = 'Typ filtru'; +$lang['edit']['sieve_type'] = 'Typ filtru'; +$lang['mailbox']['set_prefilter'] = 'Oznaฤit jako (prefilter)'; +$lang['mailbox']['set_postfilter'] = 'Oznaฤit jako (postfilter)'; +$lang['mailbox']['filters'] = 'Filtry'; +$lang['mailbox']['sync_jobs'] = 'Synchronizaฤnรญ รบlohy'; +$lang['mailbox']['inactive'] = 'Neaktivnรญ'; +$lang['edit']['validate_save'] = 'Ovฤลit a uloลพit'; + +$lang['login']['username'] = 'Jmรฉno uลพivatele'; +$lang['login']['password'] = 'Heslo'; +$lang['login']['login'] = 'Pลihlรกsit'; +$lang['login']['delayed'] = 'Pลihlรกลกenรญ bylo pozdrลพeno o %s sekund.'; + +$lang['tfa']['tfa'] = "Dvoufaktorovรฉ ovฤลovรกnรญ"; +$lang['tfa']['set_tfa'] = "Nastavenรญ zpลฏsobu dvoufaktorovรฉho ovฤลovรกnรญ"; +$lang['tfa']['yubi_otp'] = "Yubico OTP ovฤลovรกnรญ"; +$lang['tfa']['key_id'] = "Identifikรกtor vaลกeho YubiKey"; +$lang['tfa']['key_id_totp'] = "Identifikรกtor vaลกeho klรญฤe"; +$lang['tfa']['api_register'] = 'mailcow pouลพรญvรก Yubico Cloud API. Prosรญm zรญskejte API klรญฤ pro vaลกe Yubico <a href="https://upgrade.yubico.com/getapikey/" target="_blank">ZDE</a>'; +$lang['tfa']['u2f'] = "U2F ovฤลovรกnรญ"; +$lang['tfa']['none'] = "Deaktivovat"; +$lang['tfa']['delete_tfa'] = "Zakรกzat TFA"; +$lang['tfa']['disable_tfa'] = "Zakรกzat TFA do pลรญลกtรญho รบspฤลกnรฉho pลihlรกลกenรญ"; +$lang['tfa']['confirm'] = "Potvrdit"; +$lang['tfa']['totp'] = "ฤasovฤ zaloลพenรฉ OTP (Google ovฤลovรกnรญ apod.)"; +$lang['tfa']['select'] = "Prosรญm vyberte..."; +$lang['tfa']['waiting_usb_auth'] = "<i>Probรญhรก ฤekรกnรญ na USB zaลรญzenรญ...</i><br><br>Prosรญm stisknฤte tlaฤรญtko na vaลกem U2F USB zaลรญzenรญ."; +$lang['tfa']['waiting_usb_register'] = "<i>Probรญhรก ฤekรกnรญ na USB zaลรญzenรญ...</i><br><br>Prosรญm zadejte vaลกe heslo vรฝลกe a potvrฤte U2F registraci stiskem tlaฤรญtka na vaลกem U2F USB zaลรญzenรญ."; +$lang['tfa']['scan_qr_code'] = "Prosรญm oscanujte nรกsledujรญcรญ kรณd vaลกรญ aplikacรญ na ovฤลovรกnรญ nebo zadejte kรณd ruฤnฤ."; +$lang['tfa']['enter_qr_code'] = "Vรฝลก kรณd TOTP pokud vaลกe zaลรญzenรญ nemลฏลพe scanovat QR kรณdy"; +$lang['tfa']['confirm_totp_token'] = "Prosรญm potvrฤte zmฤny zadรกnรญm vygenerovanรฉho tokenu"; + +$lang['admin']['rspamd-com_settings'] = '<a href="https://rspamd.com/doc/configuration/settings.html#settings-structure" target="_blank">Rspamd dokumentace</a> + - Nรกzev nastavenรญ bude automaticky generovรกn, viz nรญลพe uvedenรฉ pลedvolby.'; + +$lang['admin']['queue_manager'] = 'Sprรกvce fronty'; +$lang['admin']['no_new_rows'] = 'ลฝรกdnรฉ dalลกรญ ลรกdky nejsou k dispozici'; +$lang['admin']['additional_rows'] = ' ลรกdkลฏ pลidรกno'; // parses to 'n additional rows were added' +$lang['admin']['private_key'] = 'Soukromรฝ klรญฤ'; +$lang['admin']['import'] = 'Importovat'; +$lang['admin']['duplicate'] = 'Duplikovat'; +$lang['admin']['import_private_key'] = 'Importovat soukromรฝ klรญฤ'; +$lang['admin']['duplicate_dkim'] = 'Duplikovat DKIM zรกznam'; +$lang['admin']['dkim_from'] = 'Od'; +$lang['admin']['dkim_to'] = 'Komu'; +$lang['admin']['dkim_from_title'] = 'Zdrojovรก domรฉna z kterรฉ se budou kopรญrovat data'; +$lang['admin']['dkim_to_title'] = 'Cรญlovรก domรฉna/y - budou pลepsรกny'; +$lang['admin']['f2b_parameters'] = 'Fail2ban'; +$lang['admin']['f2b_ban_time'] = 'ฤas blokovรกnรญ (s)'; +$lang['admin']['f2b_max_attempts'] = 'Max. pokusลฏ'; +$lang['admin']['f2b_retry_window'] = 'Retry window (s). Interval bฤhem kterรฉho se poฤรญtajรญ neรบspฤลกnรฉ pokusy o pลihlรกลกenรญ'; +$lang['admin']['f2b_netban_ipv4'] = 'Rozsah IPv4 podsรญtฤ pro provedenรญ blokovรกnรญ (8-32)'; +$lang['admin']['f2b_netban_ipv6'] = 'Rozsah IPv6 podsรญtฤ pro provedenรญ blokovรกnรญ (8-128)'; +$lang['admin']['f2b_whitelist'] = 'Sรญtฤ/klienti na Whitelistu'; +$lang['admin']['f2b_blacklist'] = 'Sรญtฤ/klienti na Blacklistu'; +$lang['admin']['f2b_list_info'] = 'Sรญt nebo klienti na blacklistu budou mรญt vลพdy vฤtลกรญ vรกhu neลพ poloลพky na whitelistu. Zรกznamy z blacklistu jsou vytvรกลeny pลi startu kontejneru. Zรกznamy ve whitelistu jsou ฤteny pokaลพdรฉ, kdyลพ je aplikovรกno blokovรกnรญ.'; +$lang['admin']['search_domain_da'] = 'Hledat domรฉny'; +$lang['admin']['r_inactive'] = 'Neaktivnรญ restrikce'; +$lang['admin']['r_active'] = 'Aktivnรญ restrikce'; +$lang['admin']['r_info'] = 'ล edรฉ/zakรกzanรฉ poloลพky v seznamu aktivnรญch omezenรญ nejsou mailcow systรฉmu znรกmy jako platnรก a nelze je pลesouvat. Neznรกmรก omezenรญ budou stejnฤ seลazena dle ฤasu jejich vรฝskytu. <br>Pro nastavenรญ lze pลidat novรฉ zรกznamy do <code>inc/vars.local.inc.php</code>.'; +$lang['admin']['dkim_key_length'] = 'Dรฉlka DKIM klรญฤe (bits)'; +$lang['admin']['dkim_key_valid'] = 'Klรญฤ je platnรฝ'; +$lang['admin']['dkim_key_unused'] = 'Klรญฤ nepouลพitรฝ'; +$lang['admin']['dkim_key_missing'] = 'Klรญฤ chybรญ'; +$lang['admin']['dkim_add_key'] = 'Pลidat ARC/DKIM klรญฤ'; +$lang['admin']['dkim_keys'] = 'ARC/DKIM klรญฤe'; +$lang['admin']['dkim_private_key'] = 'Soukromรฝ klรญฤ'; +$lang['admin']['dkim_domains_wo_keys'] = "Vybrat domรฉny bez klรญฤe"; +$lang['admin']['dkim_domains_selector'] = "Selektor"; +$lang['admin']['add'] = 'Pลidat'; +$lang['add']['add_domain_restart'] = 'Pลidat domรฉnu a restartovat SOGo'; +$lang['add']['add_domain_only'] = 'Pลidat domรฉnu'; +$lang['admin']['configuration'] = 'Nastavenรญ'; +$lang['admin']['password'] = 'Heslo'; +$lang['admin']['password_repeat'] = 'Potvrzenรญ novรฉho hesla (opakujte)'; +$lang['admin']['active'] = 'Aktivnรญ'; +$lang['admin']['inactive'] = 'Neaktivnรญ'; +$lang['admin']['action'] = 'Akce'; +$lang['admin']['add_domain_admin'] = 'Pลidat domรฉnovรฉho administrรกtora'; +$lang['admin']['add_admin'] = 'Pลidat administrรกtora'; +$lang['admin']['add_settings_rule'] = 'Pลidat nastavenรญ'; +$lang['admin']['rsetting_desc'] = 'Krรกtkรฝ popis'; +$lang['admin']['rsetting_content'] = 'Obsah pravidla'; +$lang['admin']['rsetting_none'] = 'ลฝรกdnรฉ pravidlo nenรญ k dispozici'; +$lang['admin']['rsetting_no_selection'] = 'Prosรญm vyberte pravidlo'; +$lang['admin']['rsettings_preset_1'] = 'Pro pลihlรกลกenรฉ uลพivatele vypnuto vลกe kromฤ DKIM a omezovรกnรญ provozu'; +$lang['admin']['rsettings_preset_2'] = 'Poลกtmistลi chtฤjรญ spam'; +$lang['admin']['rsettings_insert_preset'] = 'Vloลพit pลรญklad nastavenรญ "%s"'; +$lang['admin']['rsetting_add_rule'] = 'Pลidat pravidlo'; +$lang['admin']['queue_ays'] = 'Potvrฤte prosรญm, ลพe chcete odstranit vลกechny poloลพky z aktuรกlnรญ fronty.'; +$lang['admin']['arrival_time'] = 'ฤas zaลazenรญ do fronty (ฤas serveru)'; +$lang['admin']['message_size'] = 'Velikost zprรกvy'; +$lang['admin']['sender'] = 'Odesรญlatel'; +$lang['admin']['recipients'] = 'Pลรญjemci'; +$lang['admin']['admin_domains'] = 'Pลidฤlฤnรฉ domรฉny'; +$lang['admin']['domain_admins'] = 'Administrรกtoลi domรฉn'; +$lang['admin']['flush_queue'] = 'Zkusit opฤtovnฤ doruฤit frontu. (Flush queue)'; +$lang['admin']['delete_queue'] = 'Smazat vลกe'; +$lang['admin']['queue_deliver_mail'] = 'Doruฤit'; +$lang['admin']['queue_hold_mail'] = "Zadrลพet"; +$lang['admin']['queue_unhold_mail'] = 'Propustit'; +$lang['admin']['username'] = 'Jmรฉno uลพivatele'; +$lang['admin']['edit'] = 'Upravit'; +$lang['admin']['remove'] = 'Smazat'; +$lang['admin']['save'] = 'Uloลพit zmฤny'; +$lang['admin']['admin'] = 'Administrator'; +$lang['admin']['admin_details'] = 'Administrรกtoลi mailserveru'; +$lang['admin']['unchanged_if_empty'] = 'Pokud se nemฤnรญ, ponechte prรกzdnรฉ'; +$lang['admin']['yes'] = '✓'; +$lang['admin']['no'] = '✕'; +$lang['admin']['access'] = 'Pลรญstupy'; +$lang['admin']['no_record'] = 'ลฝรกdnรฝ zรกznam'; +$lang['admin']['filter_table'] = 'Tabulka filtrลฏ'; +$lang['admin']['empty'] = 'ลฝรกdnรฉ vรฝsledky'; +$lang['admin']['time'] = 'ฤas'; +$lang['admin']['last_applied'] = 'Naposledy pouลพitรฉ'; +$lang['admin']['reset_limit'] = 'Odebrat hash'; +$lang['admin']['hash_remove_info'] = 'Odebrรกnรญ hashe omezovรกnรญ provozu (pokud stรกle existuje) vyresetuje kompletnฤ jeho ฤรญtaฤ.<br> + Kaลพdรฝ hash je oznaฤen jedineฤnou barvou.'; +$lang['warning']['hash_not_found'] = 'Hash nenalezen'; +$lang['success']['hash_deleted'] = 'Hash byl smazรกn'; +$lang['admin']['authed_user'] = 'Pลihlรกลกenรฝ uลพivatel'; +$lang['admin']['priority'] = 'Priorita'; +$lang['admin']['message'] = 'Zprรกva'; +$lang['admin']['rate_name'] = 'Nรกzev (Rate name)'; +$lang['admin']['refresh'] = 'Obnovit'; +$lang['admin']['to_top'] = 'Zpฤt na zaฤรกtek'; +$lang['admin']['in_use_by'] = 'Pouลพรญvรกno'; +$lang['admin']['forwarding_hosts'] = 'Pลesmฤrovรกnรญ klientลฏ (Forwarding Hosts)'; +$lang['admin']['forwarding_hosts_hint'] = 'Pลรญchozรญ zprรกvy jsou bezpodmรญneฤnฤ akceptovรกny od vลกech zde uvedenรฝch klientลฏ. Tito klienti nebudou kontrolovรกni proti DNSBL nebo podrobeni greylistingu. Spam obdrลพenรฝ od tฤchto klientลฏ nebude nikdy odmรญtnut, ale pลรญleลพitostnฤ mลฏลพe bรฝt uloลพen do sloลพky se spamem. Nejฤastฤjลกรญm รบฤelem je zadat poลกtovnรญ servery, pro kterรฉ jste nastavili pravidlo, kterรฉ pลedรกvรก pลรญchozรญ e-maily na vรกลก poลกtovnรญ server.'; +$lang['admin']['forwarding_hosts_add_hint'] = 'Lze zadat IPv4/IPv6 adresy, sรญtฤ ve formรกtu CIDR, nรกzvy klientลฏ (kterรฉ budou pลevedeny na IP adresy) nebo nรกzvy domรฉn (kterรฉ budou pลevedeny na IP dotazovรกnรญm se DNS na SPF zรกznam nebo pokud neexistuje tak na MX zรกznam).'; +$lang['admin']['relayhosts_hint'] = 'Nastavte zde pลeposรญlรกnรญ zรกvislรฉ na odesรญlateli (sender-dependent transports), pro moลพnost je vybrat v oknฤ pro nastavenรญ domรฉny.<br> +Sluลพba pลeposรญlรกnรญ je vลพdy "smtp:". Individuรกlnรญ uลพivatelskรก nastavenรญ odchozรญch TLS politik jsou takรฉ povolena.'; +$lang['admin']['transports_hint'] = 'Poloลพky seznamu pลeposรญlรกnรญ (transport map) <b>pลetฤลพujรญ</b> poloลพky seznamu pลeposรญlรกnรญ zรกvislรฉm na odesรญlateli (sender-dependent transport)</b>.<br> +Individuรกlnรญ uลพivatelskรก nastavenรญ odchozรญch TLS politik jsou ignorovรกna a lze je vynutit pลes pลetฤลพovรกnรญ odchozรญch TLS pravidel (TLS policy map overrides). Sluลพba pลeposรญlรกnรญ je vลพdy "smtp:".<br> +Pro zjiลกtฤnรญ pลihlaลกovacรญch รบdajลฏ dalลกรญho skoku "[host]:25" se Postfix <b>vลพdy</b> dotรกลพe na "nexthop" pลed hledรกnรญm "[nexthop]:25". Toto chovรกnรญ znemoลพnuje pouลพรญt "nexthop" a "[nexthop]:25" souฤasnฤ.'; +$lang['admin']['add_relayhost_hint'] = 'Upozornฤnรญ: pลihlaลกovacรญ รบdaje (pokud existujรญ) budou uloลพeny jako prostรฝ text.'; +$lang['admin']['add_transports_hint'] = 'Upozornฤnรญ: pลihlaลกovacรญ รบdaje budou uloลพeny jako prostรฝ text.'; +$lang['admin']['host'] = 'Klient (Host)'; +$lang['admin']['source'] = 'Zdroj'; +$lang['admin']['add_forwarding_host'] = 'Pลidat pลesmฤrovรกnรญ klientลฏ (Forwarding Hosts)'; +$lang['admin']['add_relayhost'] = 'Pลidat pลeposรญlรกnรญ zรกvislรฉ na odesรญlateli (sender-dependent transport)'; +$lang['admin']['add_transport'] = 'Pลidat pลeposรญlรกnรญ (Transport)'; +$lang['admin']['relayhosts'] = 'Pลeposรญlรกnรญ zรกvislรฉ na odesรญlateli (Sender-dependent transports)'; +$lang['admin']['transport_maps'] = 'Pลeposรญlรกnรญ (Transport Maps)'; +$lang['admin']['routing'] = 'Smฤrovรกnรญ'; +$lang['admin']['credentials_transport_warning'] = '<b>Upozornฤnรญ</b>: Pลidรกnรญ poloลพky do seznamu pลeposรญlรกnรญ aktualizuje takรฉ pลihlaลกovacรญ รบdaje vลกech zรกznamลฏ s odpovรญdajรญcรญm sloupcem (nexthop).'; + +$lang['admin']['destination'] = 'Cรญl'; +$lang['admin']['nexthop'] = 'Dalลกรญ skok (Next hop)'; + +$lang['success']['forwarding_host_removed'] = "Pลesmฤrovanรฝ klient %s byl odebrรกn"; +$lang['success']['forwarding_host_added'] = "Pลesmฤrovanรฝ klient %s byl pลidรกn"; +$lang['success']['relayhost_removed'] = "Poloลพka seznamu pลeposรญlรกnรญ %s byla odebrรกna"; +$lang['success']['relayhost_added'] = "Poloลพky seznamu pลeposรญlรกnรญ %s byla pลidรกna"; +$lang['diagnostics']['dns_records'] = 'DNS zรกznamy'; +$lang['diagnostics']['dns_records_24hours'] = 'Upozornฤnรญ: Zmฤnรกm provedenรฝm v systรฉmu DNS mลฏลพe trvat aลพ 24 hodin, neลพ se na tรฉto strรกnce sprรกvnฤ zobrazรญ jejich aktuรกlnรญ stav. Tato strรกnka je urฤena pro snadnรฉ zjiลกtฤnรญ, jak nakonfigurovat DNS zรกznamy a zda jsou vลกechny vaลกe zรกznamy sprรกvnฤ uloลพeny.'; +$lang['diagnostics']['dns_records_name'] = 'Nรกzev'; +$lang['diagnostics']['dns_records_type'] = 'Typ'; +$lang['diagnostics']['dns_records_data'] = 'Sprรกvnรฝ zรกznam'; +$lang['diagnostics']['dns_records_status'] = 'Souฤasnรฝ stav'; +$lang['diagnostics']['optional'] = 'Tento zรกznam je volitelnรฝ.'; +$lang['diagnostics']['cname_from_a'] = 'Hodnota odvozena z A/AAAA zรกznamu. Toto je podporovรกno dokud zรกznam mรญลรญ na sprรกvnรฝ zdroj.'; + +$lang['admin']['relay_from'] = '"Od:" adresa'; +$lang['admin']['relay_run'] = "Provedenรญ testu"; +$lang['admin']['api_allow_from'] = "Povolenรญ pลรญstupu k API z tฤchto IP adres (oddฤlenรฉ ฤรกrkou nebo novรฝm ลรกdkem)"; +$lang['admin']['api_key'] = "API klรญฤ"; +$lang['admin']['activate_api'] = "Zapnout API"; +$lang['admin']['regen_api_key'] = "Generovat API klรญฤ"; +$lang['admin']['ban_list_info'] = "Seznam blokovanรฝch IP adres je zobrazen nรญลพe: <b>sรญลฅ (zbรฝvajรญcรญ ฤas blokovรกnรญ) - [akce]</b>.<br />IP adresy zaลazenรฉ pro odblokovรกnรญ budou odebrรกny z aktivnรญho seznamu bฤhem nฤkolika sekund.<br />ฤervenฤ oznaฤenรฉ poloลพky jsou pernamentnฤ blokovanรฉ blacklistem."; +$lang['admin']['unban_pending'] = "ฤekรก na odblokovรกnรญ"; +$lang['admin']['queue_unban'] = "odblokovat"; +$lang['admin']['no_active_bans'] = "ลฝรกdnรก aktivnรญ blokovรกnรญ"; + +$lang['admin']['quarantine'] = "Karantรฉna"; +$lang['admin']['quarantine_retention_size'] = "Poฤet zadrลพenรฝch zprรกv na poลกtovnรญ schrรกnku<br />0 znamenรก <b>neaktivnรญ</b>!"; +$lang['admin']['quarantine_max_size'] = "Maximรกlnรญ velikost v MiB (vฤtลกรญ prvky budou smazรกny)<br />0 <b>neznamenรก</b> neomezeno!"; +$lang['admin']['quarantine_exclude_domains'] = "Vylouฤenรฉ domรฉny a domรฉnovรฉ aliasy"; +$lang['admin']['quarantine_release_format'] = "Formรกt propuลกtฤnรฝch poloลพek"; +$lang['admin']['quarantine_release_format_raw'] = "Nezmฤnฤnรฝ originรกl"; +$lang['admin']['quarantine_release_format_att'] = "Jako pลรญloha"; + +$lang['admin']['ui_texts'] = "รpravy UI textลฏ"; +$lang['admin']['help_text'] = "Pลetรญลพรญ text nรกpovฤdy pod pลihlaลกovacรญm formulรกลem (HTML povoleno)"; +$lang['admin']['title_name'] = 'Nรกzev webu ("mailcow UI" title)'; +$lang['admin']['main_name'] = 'Popis pลihlaลกovacรญho formulรกลe ("mailcow UI")'; +$lang['admin']['apps_name'] = 'Popis sekce aplikacรญ ("mailcow Apps")'; + +$lang['admin']['customize'] = "Pลizpลฏsobenรญ"; +$lang['admin']['change_logo'] = "Zmฤna loga"; +$lang['admin']['logo_info'] = "Vรกลก obrรกzek bude zmenลกen na vรฝลกku 40 pixelลฏ pro hornรญ navigaฤnรญ liลกtu a na max. ลกรญลku 250px pro รบvodnรญ strรกnku."; +$lang['admin']['upload'] = "Nahrรกt"; +$lang['admin']['app_links'] = "Odkazy na aplikace"; +$lang['admin']['app_name'] = "Nรกzev aplikace"; +$lang['admin']['link'] = "Odkaz"; +$lang['admin']['remove_row'] = "Smazat ลรกdek"; +$lang['admin']['add_row'] = "Pลidat ลรกdek"; +$lang['admin']['reset_default'] = "Obnovit vรฝchozรญ nastavenรญ"; +$lang['admin']['merged_vars_hint'] = 'ล edรฉ ลรกdky byly pลidรกny z <code>vars.(local.)inc.php</code> a nelze je upravit.'; +$lang['mailbox']['waiting'] = "ฤekรกnรญ"; +$lang['mailbox']['status'] = "Stav"; +$lang['mailbox']['running'] = "Bฤลพรญ"; + +$lang['edit']['spam_score'] = "Nastavte vlastnรญ skรณre spamu"; +$lang['user']['spam_score_reset'] = "Obnovit vรฝchozรญ nastavenรญ serveru"; +$lang['edit']['spam_policy'] = "Pลidat nebo odebrat poloลพky whitelistu/blacklistu"; +$lang['edit']['spam_alias'] = "Vytvoลit nebo zmฤnit ฤasovฤ omezenรฉ (spam) aliasy"; + +$lang['danger']['img_tmp_missing'] = "Nelze ovฤลit soubor s obrรกzkem: Doฤasnรฝ soubor nenalezen"; +$lang['danger']['img_invalid'] = "Nelze ovฤลit soubor s obrรกzkem"; +$lang['danger']['invalid_mime_type'] = "ล patnรฝ mime typ"; +$lang['success']['upload_success'] = "Soubor byl รบspฤลกnฤ nahrรกn"; +$lang['success']['app_links'] = "Zmฤny odkazลฏ na aplikace uloลพeny"; +$lang['success']['ui_texts'] = "Zmฤny UI textลฏ uloลพeny"; +$lang['success']['reset_main_logo'] = "Obnovit vรฝchozรญ logo"; +$lang['success']['items_released'] = "Vybranรก poloลพka byla propuลกtฤna"; +$lang['success']['item_released'] = "Poloลพka %s byla propuลกtฤna"; +$lang['danger']['imagick_exception'] = "Chyba: Vรฝjimka programu Imagick pลi ฤtenรญ obrรกzku"; +$lang['quarantine']['quarantine'] = "Karantรฉna"; +$lang['quarantine']['learn_spam_delete'] = "Nauฤit jako spam a smazat"; +$lang['quarantine']['qinfo'] = 'Karantรฉnnรญ systรฉm uloลพรญ odmรญtnutou poลกtu do databรกze a odesรญlatel <em>nebude</em> informovรกn o nedoruฤenรฉ poลกtฤ. + <br>"' . $lang['quarantine']['learn_spam_delete'] . '" nauฤรญ systรฉm ลพe zprรกva je spam pomocรญ Bayes theoremu a takรฉ vypoฤรญtรก "fuzzy hashes" pro zakรกzรกnรญ podobnรฝch zprรกv v budoucnu. + <br>Upozornฤnรญ: Uฤenรญ se vรญcero zprรกv najednou mลฏลพe bรฝt v zรกvislosti na vaลกem systรฉmu ฤasovฤ nรกroฤnรฉ.'; +$lang['quarantine']['release'] = "Propustit"; +$lang['quarantine']['empty'] = 'ลฝรกdnรฉ vรฝsledky'; +$lang['quarantine']['toggle_all'] = 'Oznaฤit vลกe'; +$lang['quarantine']['quick_actions'] = 'Akce'; +$lang['quarantine']['remove'] = 'Smazat'; +$lang['quarantine']['received'] = "Pลijato"; +$lang['quarantine']['action'] = "Akce"; +$lang['quarantine']['rcpt'] = "Pลรญjemce"; +$lang['quarantine']['qid'] = "Rspamd QID"; +$lang['quarantine']['sender'] = "Odesรญlatel"; +$lang['quarantine']['show_item'] = "Zobrazit poloลพku"; +$lang['quarantine']['check_hash'] = "Hledat hash @ VT souboru"; +$lang['quarantine']['qitem'] = "Poloลพka v karantรฉnฤ"; +$lang['quarantine']['subj'] = "Pลedmฤt"; +$lang['quarantine']['text_plain_content'] = "Obsah (text/plain)"; +$lang['quarantine']['text_from_html_content'] = "Obsah (konvertovanรฉ html)"; +$lang['quarantine']['atts'] = "Pลรญlohy"; +$lang['warning']['fuzzy_learn_error'] = "Chyba pลi uฤenรญ Fuzzy hash: %s"; +$lang['danger']['spam_learn_error'] = "Chyba pลi uฤenรญ spamu: %s"; +$lang['success']['qlearn_spam'] = "Zprรกva ID %s byla nauฤena jako spam a smazรกna"; + +$lang['debug']['system_containers'] = 'Systรฉm a docker kontejnery'; +$lang['debug']['logs'] = 'Logy'; +$lang['debug']['log_info'] = '<p><b>Logy v pamฤti</b> jsou schromaลพฤovรกny pomocรญ Redis seznamลฏ a jsou omezeny na LOG_LINES (%d) kaลพdou minutu pro zabrรกnฤnรญ pลetฤลพovรกnรญ serveru. Nejsou navrลพeny jako trvalรฉ. Vลกechny aplikace, kterรฉ logujรญ do pamฤti zรกroveล logujรญ i do docker sluลพby podle nastavenรญ logging driveru. Logy v pamฤti jsou navrลพeny pro ladฤnรญ menลกรญch problรฉmลฏ s kontejnery.</p> + <p><b>Externรญ logy</b> jsou schromaลพฤovรกny pomocรญ API danรฉ aplikace.</p> + <p><b>Statickรฉ logy</b> jsou vฤtลกinou logy ฤinnostรญ, kterรฉ nejsou zaznamenรกvรกny do Docker sluลพby, ale je potลeba aby byly trvalรฉ (vyjรญmkou jsou API logy).</p>'; + +$lang['debug']['in_memory_logs'] = 'Logy v pamฤti'; +$lang['debug']['external_logs'] = 'Externรญ logy'; +$lang['debug']['static_logs'] = 'Statickรฉ logy'; + +$lang['debug']['disk_usage'] = 'Vyuลพitรญ disku'; +$lang['debug']['containers_info'] = "Informace o docker kontejnerech"; +$lang['debug']['restart_container'] = 'Restartovat'; + +$lang['quarantine']['release_body'] = "Vรกลก email byla pลipojen jako soubor eml k tรฉto zprรกvฤ."; +$lang['danger']['release_send_failed'] = "Zprรกvu nelze propustit: %s"; +$lang['quarantine']['release_subject'] = "Potenciรกlnฤ ลกkodlivรก poloลพka v karantรฉnฤ %s"; + +$lang['mailbox']['bcc_map'] = "BCC mapovรกnรญ"; +$lang['mailbox']['bcc_map_type'] = "Typ BCC"; +$lang['mailbox']['bcc_type'] = "Typ BCC"; +$lang['mailbox']['bcc_sender_map'] = "Mapovรกnรญ odesรญlatele (Sender map)"; +$lang['mailbox']['bcc_rcpt_map'] = "Mapovรกnรญ pลรญjemce (Recipient map)"; +$lang['mailbox']['bcc_local_dest'] = "Mรญstnรญ cรญl"; +$lang['mailbox']['bcc_destinations'] = "BCC cรญl"; +$lang['mailbox']['bcc_destination'] = "BCC cรญl"; +$lang['edit']['bcc_dest_format'] = 'BCC cรญl musรญ bรฝt jedna platnรก email adresa.'; + +$lang['mailbox']['bcc'] = "BCC"; +$lang['mailbox']['bcc_maps'] = "BCC mapovรกnรญ"; +$lang['mailbox']['bcc_to_sender'] = "Pลepnout na mapovรกnรญ odesรญlatele (sender map)"; +$lang['mailbox']['bcc_to_rcpt'] = "Pลepnout na mapovรกnรญ pลรญjemce (recipient map)"; +$lang['mailbox']['add_bcc_entry'] = "Pลidat BCC mapovรกnรญ"; +$lang['mailbox']['add_tls_policy_map'] = "Pลidat mapovรกnรญ TLS pravidel"; +$lang['mailbox']['bcc_info'] = "Mapovรกnรญ BCC se pouลพรญvรก pro tichรฉ pลedรกvรกnรญ kopiรญ vลกech zprรกv na jinou adresu. Mapovรกnรญ pลรญjemcลฏ (Recipient map) se pouลพรญvรก, pokud mรญstnรญ cรญl (local destination) pลฏsobรญ jako pลรญjemce zprรกvy. +Mapovรกnรญ odesรญlatelลฏ (Sender maps) podlรฉhรก stejnรฉmu principu. Mรญstnรญ cรญl nebude informovรกn o neรบspฤลกnรฉm doruฤenรญ."; +$lang['mailbox']['address_rewriting'] = 'รpravy adresovรกnรญ (Address rewriting)'; +$lang['mailbox']['recipient_maps'] = 'Mapovรกnรญ pลรญjemcลฏ (Recipient maps)'; +$lang['mailbox']['recipient_map'] = 'Mapovรกnรญ pลรญjemce (Recipient map)'; +$lang['mailbox']['recipient_map_info'] = 'Mapovรกnรญ pลรญjemcลฏ slouลพรญ k nahrazenรญ cรญlovรฉ adresy zprรกvy pลed doruฤenรญm.'; +$lang['mailbox']['recipient_map_old_info'] = 'Pลฏvodnรญ pลรญjemce musรญ bรฝt musรญ bรฝt platnรก email adresa nebo nรกzev domรฉny.'; +$lang['mailbox']['recipient_map_new_info'] = 'Novรฝ pลรญjemce musรญ bรฝt platnรก email adresa.'; +$lang['mailbox']['recipient_map_old'] = 'Pลฏvodnรญ pลรญjemce'; +$lang['mailbox']['recipient_map_new'] = 'Novรฝ pลijemce'; +$lang['danger']['invalid_recipient_map_new'] = 'Byl specifikovรกn ลกpatnรฝ novรฝ pลรญjemce: %s'; +$lang['danger']['invalid_recipient_map_old'] = 'Byl specifikovรกn ลกpatnรฝ pลฏvodnรญ pลรญjemce: %s'; +$lang['danger']['recipient_map_entry_exists'] = 'Poloลพka mapovรกnรญ pลรญjemcลฏ "%s" jiลพ existuje'; +$lang['success']['recipient_map_entry_saved'] = 'Poloลพka mapovรกnรญ pลรญjemcลฏ "%s" byla uloลพena'; +$lang['success']['recipient_map_entry_deleted'] = 'Mapovanรก poloลพka ID %s byla smazรกna'; +$lang['danger']['tls_policy_map_entry_exists'] = 'Poloลพka mapovรกnรญ TLS pravidel "%s" jiลพ existuje'; +$lang['success']['tls_policy_map_entry_saved'] = 'Poloลพka mapovรกnรญ TLS pravidel "%s" byla uloลพena'; +$lang['success']['tls_policy_map_entry_deleted'] = 'Mapovanรก poloลพka TLS pravidel ID %s byla smazรกna'; +$lang['mailbox']['add_recipient_map_entry'] = 'Pลidat mapovรกnรญ pลรญjemce'; +$lang['danger']['tls_policy_map_parameter_invalid'] = "Parametr zaรกsad je ลกpatnรฝ"; + +$lang['oauth2']['scope_ask_permission'] = 'Aplikace poลพรกdala o nรกsledujรญcรญ oprรกvnฤnรญ'; +$lang['oauth2']['profile'] = 'Profil'; +$lang['oauth2']['profile_desc'] = 'Zobrazenรญ osobnรญch informacรญ: uลพivatelskรฉ jmรฉno, celรฉ jmรฉno, vytvoลeno, upraveno, aktivnรญ'; +$lang['oauth2']['permit'] = 'Ovฤลenรญ aplikace'; +$lang['oauth2']['authorize_app'] = 'Ovฤลenรญ aplikace'; +$lang['oauth2']['deny'] = 'Zakรกzรกno'; +$lang['oauth2']['access_denied'] = 'Pro povolenรญ pลรญstupu pลes OAuth2 se pลihlaลกte jako vlastnรญk poลกtovnรญ schrรกnky.'; + +$lang['admin']['sys_mails'] = 'Systรฉmovรก poลกta'; +$lang['admin']['subject'] = 'Pลedmฤt'; +$lang['admin']['from'] = 'Od'; +$lang['admin']['include_exclude'] = 'Zahrnout/Vylouฤit'; +$lang['admin']['include_exclude_info'] = 'Ve vรฝchozรญm nastavenรญ (bez vรฝbฤru), jsou adresovรกny <b>vลกechny poลกtovnรญ schrรกnky</b>'; +$lang['admin']['excludes'] = 'Vylouฤit tyto pลรญjemce'; +$lang['admin']['includes'] = 'Zahrnout tyto pลijemce'; +$lang['admin']['text'] = 'Text'; +$lang['admin']['activate_send'] = 'Povolit tlaฤรญtko "Odeslat"'; +$lang['admin']['send'] = 'Odeslat'; + +$lang['warning']['ip_invalid'] = 'Pลeskoฤeno, vadnรก IP: %s'; +$lang['danger']['text_empty'] = 'Text nesmรญ bรฝt prรกzdnรฝ'; +$lang['danger']['subject_empty'] = 'Pลedmฤt nesmรญ bรฝt prรกzdnรฝ'; +$lang['danger']['from_invalid'] = 'Odesรญlat nesmรญ bรฝt prรกzdnรฝ'; diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index 5741f968..4d0352d0 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -16,6 +16,10 @@ $lang['footer']['delete_these_items'] = 'Sind Sie sicher, dass die รnderungen a $lang['footer']['delete_now'] = 'Jetzt lรถschen'; $lang['footer']['cancel'] = 'Abbrechen'; +$lang['footer']['hibp_nok'] = 'รbereinstimmung gefunden! Dieses Passwort ist potentiell gefรคhrlich!'; +$lang['footer']['hibp_ok'] = 'Keine รbereinstimmung gefunden.'; + +$lang['danger']['unlimited_quota_acl'] = "Unendliche Quota untersagt durch ACL"; $lang['danger']['mysql_error'] = "MySQL Fehler: %s"; $lang['danger']['redis_error'] = "Redis Fehler: %s"; $lang['danger']['unknown_tfa_method'] = "Unbekannte TFA Methode"; @@ -26,10 +30,16 @@ $lang['success']['verified_u2f_login'] = "U2F Anmeldung verifiziert"; $lang['success']['verified_yotp_login'] = "Yubico OTP Anmeldung verifiziert"; $lang['danger']['yotp_verification_failed'] = "Yubico OTP Verifizierung fehlgeschlagen: %s"; $lang['danger']['ip_list_empty'] = "Liste erlaubter IPs darf nicht leer sein"; +$lang['danger']['invalid_destination'] = "Ziel-Format ist ungรผltig"; +$lang['danger']['invalid_nexthop'] = "Next Hop ist ungรผltig"; +$lang['danger']['invalid_nexthop_authenticated'] = 'Dieser Next Hop existiert bereits mit abweichenden Authentifizierungsdaten. Die bestehenden Authentifizierungsdaten dieses "Next Hops" mรผssen vorab angepasst werden.'; +$lang['danger']['next_hop_interferes'] = "%s verhindert das Hinzufรผgen von Next Hop %s"; +$lang['danger']['next_hop_interferes_any'] = "Ein vorhandener Eintrag verhindert das Hinzufรผgen von Next Hop %s"; $lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI Passwort muss mindestens 6 Zeichen lang sein"; $lang['success']['rspamd_ui_pw_set'] = "Rspamd UI Passwort wurde gesetzt"; +$lang['success']['queue_command_success'] = "Queue-Aufgabe erfolgreich ausgefรผhrt"; $lang['danger']['unknown'] = "Ein unbekannter Fehler trat auf"; -$lang['danger']['malformed_username'] = "Benutzername hat falsches Format"; +$lang['danger']['malformed_username'] = "Benutzername hat ein falsches Format"; $lang['info']['awaiting_tfa_confirmation'] = "Warte auf TFA Verifizierung"; $lang['success']['logged_in_as'] = "Eingeloggt als %s"; $lang['danger']['login_failed'] = "Anmeldung fehlgeschlagen"; @@ -40,9 +50,10 @@ $lang['danger']['sieve_error'] = "Sieve Parser: %s"; $lang['danger']['value_missing'] = "Bitte alle Felder ausfรผllen"; $lang['danger']['filter_type'] = "Falscher Filtertyp"; $lang['danger']['domain_cannot_match_hostname'] = "Domain darf nicht dem Hostnamen entsprechen"; -$lang['warning']['domain_added_sogo_failed'] = "Domain wurde hinzugefรผgt; SOGo konnte nicht neugestartet werden"; +$lang['warning']['domain_added_sogo_failed'] = "Domain wurde hinzugefรผgt, aber SOGo konnte nicht neugestartet werden"; $lang['danger']['rl_timeframe'] = "Ratelimit Zeitraum ist inkorrekt"; $lang['success']['rl_saved'] = "Ratelimit fรผr Objekt %s wurde gesetzt"; +$lang['success']['acl_saved'] = "ACL fรผr Objekt %s wurde gesetzt"; $lang['success']['deleted_syncjobs'] = "Syncjobs gelรถscht: %s"; $lang['success']['deleted_syncjob'] = "Syncjobs ID %s gelรถscht"; $lang['success']['delete_filters'] = "Filter gelรถscht: %s"; @@ -58,17 +69,17 @@ $lang['danger']['private_key_error'] = "Schlรผsselfehler: %s"; $lang['danger']['map_content_empty'] = "Inhalt darf nicht leer sein"; $lang['success']['settings_map_added'] = "Regel wurde gespeichert"; $lang['danger']['settings_map_invalid'] = "Regel ID %s ist ungรผltig"; -$lang['danger']['settings_map_removed'] = "Regeln wurden entfernt: %s"; +$lang['success']['settings_map_removed'] = "Regeln wurden entfernt: %s"; $lang['danger']['invalid_host'] = "Ungรผltiger Host: %s"; -$lang['danger']['relayhost_invalid'] = "Relayhost %s ist ungรผltig"; +$lang['danger']['relayhost_invalid'] = "Mapeintrag %s ist ungรผltig"; $lang['success']['saved_settings'] = "Regel wurde gespeichert"; $lang['danger']['dkim_domain_or_sel_invalid'] = 'DKIM-Domain oder Selektor nicht korrekt: %s'; -$lang['success']['dkim_removed'] = 'DKIM-Key wurde entfernt'; -$lang['success']['dkim_added'] = 'DKIM-Key wurde hinzugefรผgt'; +$lang['success']['dkim_removed'] = 'DKIM-Key %s wurde entfernt'; +$lang['success']['dkim_added'] = 'DKIM-Key %s wurde hinzugefรผgt'; $lang['success']['dkim_duplicated'] = "DKIM-Key der Domain %s wurde auf Domain %s kopiert"; $lang['danger']['access_denied'] = 'Zugriff verweigert oder unvollstรคndige/ungรผltige Daten'; -$lang['danger']['domain_invalid'] = 'Domainname %s ist ungรผltig'; +$lang['danger']['domain_invalid'] = 'Domainname ist leer oder ungรผltig'; $lang['danger']['mailbox_quota_exceeds_domain_quota'] = 'Maximale Grรถรe fรผr Mailboxen รผberschreitet das Domain Speicherlimit'; $lang['danger']['object_is_not_numeric'] = 'Wert %s ist nicht numerisch'; $lang['success']['domain_added'] = 'Domain %s wurde angelegt'; @@ -77,7 +88,7 @@ $lang['success']['item_deleted'] = "Objekt %s wurde entfernt"; $lang['danger']['alias_empty'] = 'Alias-Adresse darf nicht leer sein'; $lang['danger']['goto_empty'] = 'Ziel-Adresse darf nicht leer sein'; $lang['danger']['policy_list_from_exists'] = 'Ein Eintrag mit diesem Wert existiert bereits'; -$lang['danger']['policy_list_from_invalid'] = 'Eintrag hat ungรผltiges Format'; +$lang['danger']['policy_list_from_invalid'] = 'Eintrag hat ein ungรผltiges Format'; $lang['danger']['alias_invalid'] = 'Alias-Adresse %s ist ungรผltig'; $lang['danger']['goto_invalid'] = 'Ziel-Adresse %s ist ungรผltig'; $lang['danger']['last_key'] = 'Letzter Key kann nicht gelรถscht werden'; @@ -94,14 +105,16 @@ $lang['success']['aliasd_modified'] = 'รnderungen an Alias-Domain %s wurden ges $lang['success']['mailbox_modified'] = 'รnderungen an Mailbox %s wurden gespeichert'; $lang['success']['resource_modified'] = "รnderungen an Ressource %s wurden gespeichert"; $lang['success']['object_modified'] = "รnderungen an Objekt %s wurden gespeichert"; -$lang['success']['f2b_modified'] = "รnderungen an Fail2ban Parametern wurden gespeichert"; +$lang['success']['f2b_modified'] = "รnderungen an Fail2ban-Parametern wurden gespeichert"; $lang['danger']['targetd_not_found'] = 'Ziel-Domain %s nicht gefunden'; $lang['success']['aliasd_added'] = 'Alias-Domain %s wurde angelegt'; $lang['success']['aliasd_modified'] = 'รnderungen an Alias-Domain %s wurden gespeichert'; $lang['success']['domain_modified'] = 'รnderungen an Domain %s wurden gespeichert'; $lang['success']['domain_admin_modified'] = 'รnderungen an Domain-Administrator %s wurden gespeichert'; $lang['success']['domain_admin_added'] = 'Domain-Administrator %s wurde angelegt'; +$lang['success']['admin_added'] = 'Administrator %s wurde angelegt'; $lang['success']['admin_modified'] = 'รnderungen am Administrator wurden gespeichert'; +$lang['success']['admin_api_modified'] = "รnderungen an API wurden gespeichert"; $lang['danger']['username_invalid'] = 'Benutzername %s kann nicht verwendet werden'; $lang['danger']['password_mismatch'] = 'Passwort-Wiederholung stimmt nicht รผberein'; $lang['danger']['password_complexity'] = 'Passwort entspricht nicht den Richtlinien'; @@ -125,9 +138,13 @@ $lang['success']['domain_removed'] = 'Domain %s wurde entfernt'; $lang['success']['alias_removed'] = 'Alias-Adresse %s wurde entfernt'; $lang['success']['alias_domain_removed'] = 'Alias-Domain %s wurde entfernt'; $lang['success']['domain_admin_removed'] = 'Domain-Administrator %s wurde entfernt'; +$lang['success']['admin_removed'] = 'Administrator %s wurde entfernt'; $lang['success']['mailbox_removed'] = 'Mailbox %s wurde entfernt'; -$lang['success']['eas_reset'] = "ActiveSync Gerรคt des Benutzers %s wurden zurรผckgesetzt"; +$lang['success']['eas_reset'] = "ActiveSync Gerรคt des Benutzers %s wurde zurรผckgesetzt"; +$lang['success']['sogo_profile_reset'] = "ActiveSync Gerรคt des Benutzers %s wurde zurรผckgesetzt"; $lang['success']['resource_removed'] = 'Ressource %s wurde entfernt'; +$lang['warning']['cannot_delete_self'] = 'Kann derzeit eingeloggten Benutzer nicht entfernen'; +$lang['warning']['no_active_admin'] = 'Kann letzten aktiven Administrator nicht deaktivieren'; $lang['danger']['max_quota_in_use'] = 'Mailbox Speicherplatzlimit muss grรถรer oder gleich %d MiB sein'; $lang['danger']['domain_quota_m_in_use'] = 'Domain Speicherplatzlimit muss grรถรer oder gleich %d MiB sein'; $lang['danger']['mailboxes_in_use'] = 'Maximale Anzahl an Mailboxen muss grรถรer oder gleich %d sein'; @@ -155,7 +172,7 @@ $lang['user']['new_password_description'] = 'Mindestanforderung: 6 Zeichen lang, $lang['user']['spam_aliases'] = 'Temporรคre E-Mail Aliasse'; $lang['user']['alias'] = 'Alias'; $lang['user']['shared_aliases'] = 'Geteilte Alias-Adressen'; -$lang['user']['shared_aliases_desc'] = 'Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen wie die des Spam-Filters oder der Verschlรผsselungsrichtlinie berรผcksichtigt. Entsprechende Spam-Filter kรถnnen lediglich von einem Administrator vorgenommen werden.'; +$lang['user']['shared_aliases_desc'] = 'Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen, wie die des Spam-Filters oder der Verschlรผsselungsrichtlinie, berรผcksichtigt. Entsprechende Spam-Filter kรถnnen lediglich von einem Administrator vorgenommen werden.'; $lang['user']['direct_aliases'] = 'Direkte Alias-Adressen'; $lang['user']['direct_aliases_desc'] = 'Nur direkte Alias-Adressen werden fรผr benutzerdefinierte Einstellungen berรผcksichtigt.'; $lang['user']['is_catch_all'] = 'Ist Catch-All Adresse fรผr Domain(s)'; @@ -169,6 +186,7 @@ $lang['user']['alias_time_left'] = 'Zeit verbleibend'; $lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; $lang['user']['alias_select_validity'] = 'Bitte Gรผltigkeit auswรคhlen'; $lang['user']['sync_jobs'] = 'Sync Jobs'; +$lang['user']['expire_in'] = 'Ungรผltig in'; $lang['user']['hour'] = 'Stunde'; $lang['user']['hours'] = 'Stunden'; $lang['user']['day'] = 'Tag'; @@ -183,10 +201,9 @@ $lang['user']['spamfilter_bl_desc'] = 'Fรผr E-Mail-Adressen, die vom Spamfilter $lang['user']['spamfilter_table_rule'] = 'Regel'; $lang['user']['spamfilter_table_action'] = 'Aktion'; $lang['user']['spamfilter_table_empty'] = 'Keine Eintrรคge vorhanden'; -$lang['user']['spamfilter_table_remove'] = 'entfernen'; +$lang['user']['spamfilter_table_remove'] = 'Entfernen'; $lang['user']['spamfilter_table_add'] = 'Eintrag hinzufรผgen'; $lang['user']['spamfilter_behavior'] = 'Bewertung'; -$lang['user']['spamfilter_default_score'] = 'Spam-Score:'; $lang['user']['spamfilter_green'] = 'Grรผn: Die Nachricht ist kein Spam'; $lang['user']['spamfilter_yellow'] = 'Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben'; $lang['user']['spamfilter_red'] = 'Rot: Die Nachricht ist eindeutig Spam und wird vom Server abgelehnt'; @@ -211,10 +228,15 @@ $lang['user']['tag_in_none'] = 'Nichts tun'; $lang['user']['tag_help_explain'] = 'Als Unterordner: Es wird ein Ordner mit dem Namen des Tags unterhalb der Inbox erstellt ("INBOX/Facebook").<br> In Betreff: Der Name des Tags wird dem Betreff angefรผgt, etwa "[Facebook] Meine Neuigkeiten".'; $lang['user']['tag_help_example'] = 'Beispiel fรผr eine getaggte E-Mail-Adresse: ich<b>+Facebook</b>@example.org'; + $lang['user']['eas_reset'] = 'ActiveSync Gerรคte-Cache zurรผcksetzen'; $lang['user']['eas_reset_now'] = 'Jetzt zurรผcksetzen'; $lang['user']['eas_reset_help'] = 'In vielen Fรคllen kann ein ActiveSync Profil durch das Zurรผcksetzen des Caches repariert werden.<br><b>Vorsicht:</b> Alle Elemente werden erneut heruntergeladen!'; +$lang['user']['sogo_profile_reset'] = 'SOGo Profil zurรผcksetzen'; +$lang['user']['sogo_profile_reset_now'] = 'Profil jetzt zurรผcksetzen'; +$lang['user']['sogo_profile_reset_help'] = 'Das Profil wird zuzรผglich aller Daten <b>unwiederbringlich gelรถscht</b>.'; + $lang['user']['encryption'] = 'Verschlรผsselung'; $lang['user']['username'] = 'Benutzername'; $lang['user']['last_run'] = 'Letzte Ausfรผhrung'; @@ -226,16 +248,30 @@ $lang['user']['edit'] = 'Bearbeiten'; $lang['user']['remove'] = 'Entfernen'; $lang['user']['create_syncjob'] = 'Neuen Sync-Job erstellen'; -$lang['start']['mailcow_apps_detail'] = 'Verwenden Sie mailcow Apps, um E-Mails abzurufen, Kalender- und Kontakte zu verwalten und vieles mehr.'; +$lang['start']['mailcow_apps_detail'] = 'Verwenden Sie mailcow Apps, um E-Mails abzurufen, Kalender und Kontakte zu verwalten und vieles mehr.'; $lang['start']['mailcow_panel_detail'] = '<b>Domain-Administratoren</b> erstellen, verรคndern oder lรถschen Mailboxen, verwalten die Domรคne und sehen sonstige Einstellungen ein.<br> Als <b>Mailbox-Benutzer</b> erstellen Sie hier zeitlich limitierte Aliasse, รคndern das Verhalten des Spamfilters, setzen ein neues Passwort und vieles mehr.'; $lang['start']['imap_smtp_server_auth_info'] = 'Bitte verwenden Sie Ihre vollstรคndige E-Mail-Adresse sowie das PLAIN-Authentifizierungsverfahren.<br> Ihre Anmeldedaten werden durch die obligatorische Verschlรผsselung entgegen des Begriffes "PLAIN" nicht unverschlรผsselt รผbertragen.'; $lang['start']['help'] = 'Hilfe ein-/ausblenden'; $lang['header']['mailcow_settings'] = 'Konfiguration'; -$lang['header']['administration'] = 'Administration'; -$lang['header']['mailboxes'] = 'Mailboxen'; +$lang['header']['administration'] = 'Server-Konfiguration'; +$lang['header']['mailboxes'] = 'E-Mail-Setup'; $lang['header']['user_settings'] = 'Benutzereinstellungen'; +$lang['header']['quarantine'] = "Quarantรคne"; +$lang['header']['debug'] = "Systeminformation"; +$lang['quarantine']['disabled_by_config'] = "Die derzeitige Konfiguration deaktiviert die Funktion des Quarantรคne-Systems."; +$lang['mailbox']['tls_policy_maps'] = 'TLS-Richtlinien'; +$lang['mailbox']['tls_policy_maps_long'] = 'Ausgehende TLS-Richtlinien'; +$lang['mailbox']['tls_policy_maps_info'] = 'Nachstehende Richtlinien erzwingen TLS-Transportregeln unabhรคngig von TLS-Richtlinieneinstellungen eines Benutzers.<br> + Fรผr weitere Informationen zur Syntax sollte <a href="http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps" target="_blank">die "smtp_tls_policy_maps" Dokumentation</a> konsultiert werden.'; +$lang['mailbox']['tls_enforce_in'] = 'TLS eingehend erzwingen'; +$lang['mailbox']['tls_enforce_out'] = 'TLS ausgehend erzwingen'; +$lang['mailbox']['tls_map_dest'] = 'Ziel'; +$lang['mailbox']['tls_map_dest_info'] = 'Beispiele: example.org, .example.org, mail@example.org, [mail.example.org]:25'; +$lang['mailbox']['tls_map_policy'] = 'Richtlinie'; +$lang['mailbox']['tls_map_parameters'] = 'Parameter'; +$lang['mailbox']['tls_map_parameters_info'] = 'Leer oder Parameter, Beispiele: protocols=!SSLv2 ciphers=medium exclude=3DES'; $lang['mailbox']['booking_0'] = 'Immer als verfรผgbar anzeigen'; $lang['mailbox']['booking_lt0'] = 'Unbegrenzt, jedoch anzeigen, wenn gebucht'; $lang['mailbox']['booking_custom'] = 'Benutzerdefiniertes Limit'; @@ -252,6 +288,7 @@ $lang['mailbox']['description'] = 'Beschreibung'; $lang['mailbox']['resources'] = 'Ressourcen'; $lang['mailbox']['domains'] = 'Domains'; $lang['admin']['domain_s'] = 'Domain(s)'; +$lang['mailbox']['mailbox'] = 'Mailbox'; $lang['mailbox']['mailboxes'] = 'Mailboxen'; $lang['mailbox']['mailbox_quota'] = 'Max. Grรถรe einer Mailbox'; $lang['mailbox']['domain_quota'] = 'Gesamtspeicher'; @@ -264,8 +301,6 @@ $lang['mailbox']['target_address'] = 'Ziel-Adresse'; $lang['mailbox']['username'] = 'Benutzername'; $lang['mailbox']['fname'] = 'Name'; $lang['mailbox']['filter_table'] = 'Filtern'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'Prozentualer Gebrauch'; $lang['mailbox']['msg_num'] = 'Anzahl Nachrichten'; $lang['mailbox']['remove'] = 'Entfernen'; @@ -286,6 +321,7 @@ $lang['mailbox']['owner'] = 'Besitzer'; $lang['mailbox']['mins_interval'] = 'Intervall (min)'; $lang['mailbox']['last_run'] = 'Letzte Ausfรผhrung'; $lang['mailbox']['last_run_reset'] = 'Als nรคchstes ausfรผhren'; +$lang['mailbox']['excludes'] = 'Ausschlรผsse'; $lang['mailbox']['sieve_info'] = 'Es kรถnnen mehrere Filter pro Benutzer existieren, aber nur ein Filter eines Typs (Pre-/Postfilter) kann gleichzeitig aktiv sein.<br> Die Ausfรผhrung erfolgt in nachstehender Reihenfolge. Ein fehlgeschlagenes Script sowie der Befehl "keep;" stoppen die weitere Verarbeitung <b>nicht</b>.<br> Prefilter โ User scripts โ Postfilter โ <a href="https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after" target="_blank">global sieve postfilter</a>'; @@ -310,8 +346,12 @@ $lang['edit']['max_mailboxes'] = 'Max. Mailboxanzahl:'; $lang['edit']['title'] = 'Objekt bearbeiten'; $lang['edit']['target_address'] = 'Ziel-Adresse(n) <small>(getrennt durch Komma)</small>:'; $lang['edit']['active'] = 'Aktiv'; +$lang['edit']['gal'] = 'Globales Adressbuch'; +$lang['edit']['gal_info'] = 'Das Globale Adressbuch enthรคlt alle Objekte einer Domain und kann durch keinen Benutzer editiert werden. <b>Zum Anwenden einer รnderung muss SOGo neugestartet werden.</b>'; $lang['edit']['force_pw_update'] = 'Erzwinge Passwortรคnderung bei nรคchstem Login'; $lang['edit']['force_pw_update_info'] = 'Dem Benutzer wird lediglich der Zugang zur mailcow UI ermรถglicht.'; +$lang['edit']['sogo_access'] = 'SOGo Zugriffsrecht'; +$lang['edit']['sogo_access_info'] = 'Zugriff auf SOGo erlauben oder verbieten. Diese Einstellung hat weder Einfluss auf den Zugang sonstiger Dienste noch entfernt sie ein vorhandenes SOGo Benutzerprofil.'; $lang['edit']['target_domain'] = 'Ziel-Domain:'; $lang['edit']['password'] = 'Passwort:'; $lang['edit']['password_repeat'] = 'Passwort (Wiederholung):'; @@ -332,16 +372,62 @@ $lang['edit']['relay_all_info'] = '<small>Wenn Sie <b>nicht</b> alle Empfรคnger- $lang['edit']['full_name'] = 'Voller Name'; $lang['edit']['quota_mb'] = 'Speicherplatz (MiB)'; $lang['edit']['sender_acl'] = 'Darf Nachrichten versenden als'; +$lang['edit']['sender_acl_disabled'] = 'โณ <span class="label label-danger">Absenderprรผfung deaktiviert</span>'; +$lang['user']['sender_acl_disabled'] = '<span class="label label-danger">Absenderprรผfung deaktiviert</span>'; $lang['edit']['previous'] = 'Vorherige Seite'; $lang['edit']['unchanged_if_empty'] = 'Unverรคndert, wenn leer'; $lang['edit']['dont_check_sender_acl'] = 'Absender fรผr Domain %s u. Alias-Dom. nicht prรผfen'; $lang['edit']['multiple_bookings'] = 'Mehrfaches Buchen'; $lang['edit']['kind'] = 'Art'; $lang['edit']['resource'] = 'Ressource'; +$lang['edit']['public_comment'] = 'รffentlicher Kommentar'; +$lang['mailbox']['public_comment'] = 'รffentlicher Kommentar'; +$lang['edit']['private_comment'] = 'Privater Kommentar'; +$lang['mailbox']['private_comment'] = 'Privater Kommentar'; +$lang['edit']['comment_info'] = 'Ein privater Kommentar ist fรผr den Benutzer nicht einsehbar. Ein รถffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.'; +$lang['add']['public_comment'] = 'รffentlicher Kommentar'; +$lang['add']['private_comment'] = 'Privater Kommentar'; +$lang['add']['comment_info'] = 'Ein privater Kommentar ist fรผr den Benutzer nicht einsehbar. Ein รถffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.'; -$lang['add']['syncjob'] = 'Sync-Job erstellen'; +$lang['acl']['spam_alias'] = 'Temporรคre E-Mail Aliasse'; +$lang['acl']['tls_policy'] = 'Verschlรผsselungsrichtlinie'; +$lang['acl']['spam_score'] = 'Spam-Bewertung'; +$lang['acl']['spam_policy'] = 'Blacklist/Whitelist'; +$lang['acl']['delimiter_action'] = 'Delimiter Aktionen (tags)'; +$lang['acl']['syncjobs'] = 'Sync Jobs'; +$lang['acl']['eas_reset'] = 'EAS-Cache zurรผcksetzen'; +$lang['acl']['sogo_profile_reset'] = 'SOGo Profil zurรผcksetzen'; +$lang['acl']['quarantine'] = 'Quarantรคne-Aktionen'; +$lang['acl']['quarantine_notification'] = 'Quarantรคne-Benachrichtigung'; +$lang['acl']['quarantine_attachments'] = 'Anhรคnge aus Quarantรคne'; +$lang['acl']['alias_domains'] = 'Alias-Domains hinzufรผgen'; +$lang['acl']['login_as'] = 'Einloggen als Mailbox-Benutzer'; +$lang['acl']['bcc_maps'] = 'BCC Maps'; +$lang['acl']['filters'] = 'Filter'; +$lang['acl']['ratelimit'] = 'Rate limit'; +$lang['acl']['recipient_maps'] = 'Empfรคngerumschreibungen'; +$lang['acl']['unlimited_quota'] = 'Unendliche Quota fรผr Mailboxen'; +$lang['acl']['prohibited'] = 'Untersagt durch Richtlinie'; + +$lang['mailbox']['quarantine_notification'] = 'Quarantรคne-Benachrichtigung'; +$lang['mailbox']['never'] = 'Niemals'; +$lang['mailbox']['hourly'] = 'Stรผndlich'; +$lang['mailbox']['daily'] = 'Tรคglich'; +$lang['mailbox']['weekly'] = 'Wรถchentlich'; +$lang['user']['quarantine_notification'] = 'Quarantรคne-Benachrichtigung'; +$lang['user']['never'] = 'Niemals'; +$lang['user']['hourly'] = 'Stรผndlich'; +$lang['user']['daily'] = 'Tรคglich'; +$lang['user']['weekly'] = 'Wรถchentlich'; +$lang['user']['quarantine_notification_info'] = 'Wurde รผber eine E-Mail in Quarantรคne informiert, wird sie als "benachrichtigt" markiert und keine weitere Benachrichtigung zu dieser E-Mail versendet.'; + +$lang['add']['generate'] = 'generieren'; +$lang['add']['syncjob'] = 'Syncjob hinzufรผgen'; $lang['add']['syncjob_hint'] = 'Passwรถrter werden unverschlรผsselt abgelegt!'; -$lang['add']['hostname'] = 'Servername'; +$lang['add']['hostname'] = 'Host'; +$lang['add']['destination'] = 'Ziel'; +$lang['add']['nexthop'] = 'Next Hop'; +$lang['edit']['nexthop'] = 'Next Hop'; $lang['add']['port'] = 'Port'; $lang['add']['username'] = 'Benutzername'; $lang['add']['enc_method'] = 'Verschlรผsselung'; @@ -382,7 +468,7 @@ $lang['add']['select'] = 'Bitte auswรคhlen'; $lang['add']['target_domain'] = 'Ziel-Domain'; $lang['add']['kind'] = 'Art'; $lang['add']['mailbox_username'] = 'Benutzername (linker Teil der E-Mail-Adresse)'; -$lang['add']['full_name'] = 'Vor- und Zuname'; +$lang['add']['full_name'] = 'Vor- und Nachname'; $lang['add']['quota_mb'] = 'Speicherplatz (MiB)'; $lang['add']['select_domain'] = 'Bitte zuerst eine Domain auswรคhlen'; $lang['add']['password'] = 'Passwort'; @@ -442,12 +528,12 @@ $lang['admin']['import'] = 'Importieren'; $lang['admin']['duplicate'] = 'Duplizieren'; $lang['admin']['import_private_key'] = 'Private Key importieren'; $lang['admin']['duplicate_dkim'] = 'DKIM duplizieren'; -$lang['admin']['f2b_parameters'] = 'Fail2ban Parameter'; -$lang['admin']['f2b_ban_time'] = 'Banzeit (s)'; +$lang['admin']['f2b_parameters'] = 'Fail2ban-Parameter'; +$lang['admin']['f2b_ban_time'] = 'Bannzeit (s)'; $lang['admin']['f2b_max_attempts'] = 'Max. Versuche'; $lang['admin']['f2b_retry_window'] = 'Wiederholungen im Zeitraum von (s)'; -$lang['admin']['f2b_netban_ipv4'] = 'Netzbereich fรผr IPv4 Bans (8-32)'; -$lang['admin']['f2b_netban_ipv6'] = 'Netzbereich fรผr IPv6 Bans (8-128)'; +$lang['admin']['f2b_netban_ipv4'] = 'Netzbereich fรผr IPv4-Bans (8-32)'; +$lang['admin']['f2b_netban_ipv6'] = 'Netzbereich fรผr IPv6-Bans (8-128)'; $lang['admin']['f2b_whitelist'] = 'Whitelist fรผr Netzwerke und Hosts'; $lang['admin']['r_inactive'] = 'Inaktive Restriktionen'; $lang['admin']['r_active'] = 'Aktive Restriktionen'; @@ -464,6 +550,7 @@ $lang['admin']['dkim_from_title'] = 'Quellobjekt fรผr Duplizierung'; $lang['admin']['dkim_to_title'] = 'Ziel-Objekt(e) werden รผberschrieben'; $lang['admin']['dkim_domains_wo_keys'] = "Domains mit fehlenden Keys auswรคhlen"; $lang['admin']['add'] = 'Hinzufรผgen'; +$lang['admin']['queue_manager'] = 'Queue Manager'; $lang['add']['add_domain_restart'] = 'Domain hinzufรผgen und SOGo neustarten'; $lang['add']['add_domain_only'] = 'Nur Domain hinzufรผgen'; $lang['admin']['configuration'] = 'Konfiguration'; @@ -473,6 +560,7 @@ $lang['admin']['active'] = 'Aktiv'; $lang['admin']['inactive'] = 'Inaktiv'; $lang['admin']['action'] = 'Aktion'; $lang['admin']['add_domain_admin'] = 'Domain-Administrator hinzufรผgen'; +$lang['admin']['domain_admin'] = 'Administrator hinzufรผgen'; $lang['admin']['add_settings_rule'] = 'Rspamd Regel hinzufรผgen'; $lang['admin']['rsetting_desc'] = 'Kurze Beschreibung'; $lang['admin']['rsetting_content'] = 'Regelinhalt'; @@ -483,6 +571,13 @@ $lang['admin']['rsettings_preset_2'] = 'Spam an Postmaster-Addressen nicht block $lang['admin']['rsettings_insert_preset'] = 'Beispiel "%s" laden'; $lang['admin']['rsetting_add_rule'] = 'Regel hinzufรผgen'; $lang['admin']['admin_domains'] = 'Domain-Zuweisungen'; +$lang['admin']['queue_ays'] = 'Soll die derzeitige Queue wirklich komplett bereinigt werden?'; +$lang['admin']['arrival_time'] = 'Ankunftszeit (Serverzeit)'; +$lang['admin']['message_size'] = 'Nachrichtengrรถรe'; +$lang['admin']['sender'] = 'Sender'; +$lang['admin']['recipients'] = 'Empfรคnger'; +$lang['admin']['flush_queue'] = 'Flush Queue'; +$lang['admin']['delete_queue'] = 'Alle lรถschen'; $lang['admin']['domain_admins'] = 'Domain-Administratoren'; $lang['admin']['username'] = 'Benutzername'; $lang['admin']['edit'] = 'Bearbeiten'; @@ -491,27 +586,51 @@ $lang['admin']['save'] = 'รnderungen speichern'; $lang['admin']['admin'] = 'Administrator'; $lang['admin']['admin_details'] = 'Administrator bearbeiten'; $lang['admin']['unchanged_if_empty'] = 'Unverรคndert, wenn leer'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'Zugang'; $lang['admin']['no_record'] = 'Kein Eintrag'; $lang['admin']['filter_table'] = 'Tabelle Filtern'; $lang['admin']['empty'] = 'Keine Eintrรคge vorhanden'; $lang['admin']['time'] = 'Zeit'; +$lang['admin']['last_applied'] = 'Zuletzt angewendet'; +$lang['admin']['reset_limit'] = 'Hash entfernen'; +$lang['admin']['hash_remove_info'] = 'Das Entfernen eines Ratelimit Hashes - sofern noch existent - bewirkt den Reset gezรคhlter Nachrichten dieses Elements.<br> + Jeder Hash wird durch eine eindeutige Farbe gekennzeichnet.'; +$lang['warning']['hash_not_found'] = 'Hash nicht gefunden'; +$lang['success']['hash_deleted'] = 'Hash wurde gelรถscht'; +$lang['admin']['authed_user'] = 'Auth. Benutzer'; $lang['admin']['priority'] = 'Gewichtung'; $lang['admin']['refresh'] = 'Neu laden'; $lang['admin']['to_top'] = 'Nach oben'; $lang['admin']['in_use_by'] = 'Verwendet von'; +$lang['admin']['rate_name'] = 'Rate name'; $lang['admin']['message'] = 'Nachricht'; $lang['admin']['forwarding_hosts'] = 'Weiterleitungs-Hosts'; $lang['admin']['forwarding_hosts_hint'] = 'Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die รผbliche Verwendung fรผr diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.'; $lang['admin']['forwarding_hosts_add_hint'] = 'Sie kรถnnen entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelรถst werden), oder Domainnamen (die zu IP-Adressen aufgelรถst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.'; -$lang['admin']['relayhosts_hint'] = 'Erstellen Sie Relayhosts, um diese im Einstellungsdialog einer Domain auszuwรคhlen.'; -$lang['admin']['add_relayhost_add_hint'] = 'Bitte beachten Sie, dass Relayhost Anmeldedaten im Klartext gespeichert werden.'; +$lang['admin']['relayhosts_hint'] = 'Erstellen Sie senderabhรคngige Transporte, um diese im Einstellungsdialog einer Domain auszuwรคhlen.<br> + Der Transporttyp lautet immer "smtp:". Benutzereinstellungen bezรผglich Verschlรผsselungsrichtlinie werden beim Transport berรผcksichtigt.'; +$lang['admin']['transports_hint'] = 'โ Transport Maps <b>รผberwiegen</b> senderabhรคngige Transport Maps. +โ Transport Maps ignorieren Mailbox-Einstellungen fรผr ausgehende Verschlรผsselung. Eine serverweite TLS-Richtlinie wird jedoch angewendet.<br> +โ Der Transport erfolgt immer via "smtp:".<br> +โ Adressen, die mit "/localhost$/" รผbereinstimmen, werden immer via "local:" transportiert, daher sind sie von einer Zieldefinition "*" ausgeschlossen.<br> +โ Die Authentifizierung wird anhand des "Next hop" Parameters ermittelt. Hierbei wรผrde bei einem beispielhaften Wert "[host]:25" immer zuerst "host" abfragt und <b>erst im Anschluss</b> "[host]:25". Dieses Verhalten schlieรt die <b>gleichzeitige Verwendung</b> von Eintrรคgen der Art "host" sowie "[host]:25" aus.'; +$lang['admin']['add_relayhost_hint'] = 'Bitte beachten Sie, dass Anmeldedaten unverschlรผsselt gespeichert werden.<br> + Angelegte Transporte dieser Art sind <b>senderabhรคngig</b> und mรผssen erst einer Domain zugewiesen werden, bevor sie als Transport verwendet werden.<br> + Diese Einstellungen entsprechen demach <i>nicht</i> dem "relayhost" Parameter in Postfix.'; +$lang['admin']['add_transports_hint'] = 'Bitte beachten Sie, dass Anmeldedaten unverschlรผsselt gespeichert werden.'; $lang['admin']['host'] = 'Host'; $lang['admin']['source'] = 'Quelle'; $lang['admin']['add_forwarding_host'] = 'Weiterleitungs-Host hinzufรผgen'; -$lang['admin']['add_relayhost'] = 'Relayhost hinzufรผgen'; +$lang['admin']['add_relayhost'] = 'Senderabhรคngigen Transport hinzufรผgen'; +$lang['admin']['add_transport'] = 'Transport hinzufรผgen'; +$lang['admin']['relayhosts'] = 'Senderabhรคngige Transport Maps'; +$lang['admin']['transport_maps'] = 'Transport Maps'; +$lang['admin']['routing'] = 'Routing'; +$lang['admin']['credentials_transport_warning'] = '<b>Warnung</b>: Das Hinzufรผgen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Eintrรคge mit identischem Host.'; + +$lang['admin']['destination'] = 'Ziel'; +$lang['admin']['nexthop'] = 'Next Hop'; + $lang['admin']['api_allow_from'] = "IP-Adressen fรผr Zugriff"; $lang['admin']['api_key'] = "API-Key"; $lang['admin']['activate_api'] = "API aktivieren"; @@ -521,17 +640,31 @@ $lang['admin']['unban_pending'] = "ausstehend"; $lang['admin']['queue_unban'] = "Unban einreihen"; $lang['admin']['no_active_bans'] = "Keine aktiven Bans"; +$lang['admin']['quota_notifications'] = "Quota Benachrichtigungen"; +$lang['admin']['quota_notifications_vars'] = "{{percent}} entspricht der aktuellen Quota in Prozent<br>{{username}} entspricht dem Mailbox-Namen"; +$lang['admin']['rspamd_settings_map'] = "Rspamd Settings Map"; $lang['admin']['quarantine'] = "Quarantรคne"; -$lang['admin']['quarantine_retention_size'] = "Rรผckhaltungen pro Mailbox<br />0 bedeutet <b>inaktiv</b>!"; -$lang['admin']['quarantine_max_size'] = "Maximale Grรถรe in MiB (grรถรere Elemente werden verworfen)<br />0 bedeutet <b>nicht</b> unlimitert!"; -$lang['admin']['quarantine_exclude_domains'] = "Domains und Alias-Domains ausschlieรen:"; +$lang['admin']['active_rspamd_settings_map'] = "Derzeit aktive Settings Map"; +$lang['admin']['quota_notifications_info'] = "Quota Benachrichtigungen werden an Mailboxen versendet, die 80 respektive 95 Prozent der zur Verfรผgung stehenden Quota รผberschreiten."; +$lang['admin']['quarantine_retention_size'] = "Rรผckhaltungen pro Mailbox:<br><small>0 bedeutet <b>inaktiv</b>.</small>"; +$lang['admin']['quarantine_max_size'] = "Maximale Grรถรe in MiB (grรถรere Elemente werden verworfen):<br><small>0 bedeutet <b>nicht</b> unlimitert.</small>"; +$lang['admin']['quarantine_exclude_domains'] = "Domains und Alias-Domains ausschlieรen"; +$lang['admin']['quarantine_notification_sender'] = "Benachrichtigungs-E-Mail Absender"; +$lang['admin']['quota_notification_sender'] = "Benachrichtigungs-E-Mail Absender"; +$lang['admin']['quarantine_notification_subject'] = "Benachrichtigungs-E-Mail Betreff"; +$lang['admin']['quota_notification_subject'] = "Benachrichtigungs-E-Mail Betreff"; +$lang['admin']['quarantine_notification_html'] = "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>"; +$lang['admin']['quota_notification_html'] = "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>"; +$lang['admin']['quarantine_release_format'] = "Format freigegebener Mails"; +$lang['admin']['quarantine_release_format_raw'] = "Unverรคndertes Original"; +$lang['admin']['quarantine_release_format_att'] = "Als Anhang"; $lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt"; $lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefรผgt"; -$lang['success']['relayhost_removed'] = "Relayhost %s wurde entfernt"; -$lang['success']['relayhost_added'] = "Relayhost %s wurde hinzugefรผgt"; +$lang['success']['relayhost_removed'] = "Mapeintrag %s wurde entfernt"; +$lang['success']['relayhost_added'] = "Mapeintrag %s wurde hinzugefรผgt"; $lang['diagnostics']['dns_records'] = 'DNS-Eintrรคge'; -$lang['diagnostics']['dns_records_24hours'] = 'Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis รnderungen an Ihren DNS-Eintrรคgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte fรผr DNS-Eintrรคge zu anzuzeigen und zu รผberprรผfen, ob die Daten im DNS hinterlegt sind.'; +$lang['diagnostics']['dns_records_24hours'] = 'Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis รnderungen an Ihren DNS-Eintrรคgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte fรผr DNS-Eintrรคge anzuzeigen und zu รผberprรผfen, ob die Daten im DNS hinterlegt sind.'; $lang['diagnostics']['dns_records_name'] = 'Name'; $lang['diagnostics']['dns_records_type'] = 'Typ'; $lang['diagnostics']['dns_records_data'] = 'Korrekte Daten'; @@ -543,6 +676,8 @@ $lang['admin']['relay_run'] = "Test durchfรผhren"; $lang['mailbox']['waiting'] = "Wartend"; $lang['mailbox']['status'] = "Status"; $lang['mailbox']['running'] = "In Ausfรผhrung"; +$lang['mailbox']['enable_x'] = "Aktiviere"; +$lang['mailbox']['disable_x'] = "Deaktiviere"; $lang['admin']['ui_texts'] = "UI Label und Texte"; $lang['admin']['help_text'] = "Hilfstext unter Login-Maske (HTML zulรคssig)"; @@ -563,10 +698,12 @@ $lang['admin']['reset_default'] = "Zurรผcksetzen auf Standard"; $lang['admin']['merged_vars_hint'] = 'Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und kรถnnen hier nicht verรคndert werden.'; $lang['edit']['spam_score'] = "Einen benutzerdefiniterten Spam-Score festlegen"; +$lang['user']['spam_score_reset'] = "Auf Server-Standard zurรผcksetzen"; $lang['edit']['spam_policy'] = "Hinzufรผgen und Entfernen von Eintrรคgen in White- und Blacklists"; $lang['edit']['spam_alias'] = "Anpassen temporรคrer Alias-Adressen"; $lang['danger']['img_tmp_missing'] = "Grafik konnte nicht validiert werden: Erstellung temporรคrer Datei fehlgeschlagen"; +$lang['danger']['comment_too_long'] = "Kommentarfeld darf maximal 160 Zeichen enthalten"; $lang['danger']['img_invalid'] = "Grafik konnte nicht validiert werden"; $lang['danger']['invalid_mime_type'] = "Grafik konnte nicht validiert werden: Ungรผltiger MIME-Type"; $lang['success']['upload_success'] = "Datei wurde erfolgreich hochgeladen"; @@ -597,13 +734,16 @@ $lang['quarantine']['subj'] = "Betreff"; $lang['quarantine']['text_plain_content'] = "Inhalt (text/plain)"; $lang['quarantine']['text_from_html_content'] = "Inhalt (html, konvertiert)"; $lang['quarantine']['atts'] = "Anhรคnge"; -$lang['danger']['fuzzy_learn_error'] = "Fuzzy Lernfehler: %s"; +$lang['quarantine']['low_danger'] = "Niedrige Gefahr"; +$lang['quarantine']['neutral_danger'] = "Neutral/ohne Bewertung"; +$lang['quarantine']['medium_danger'] = "Mittlere Gefahr"; +$lang['quarantine']['high_danger'] = "Hohe Gefahr"; +$lang['quarantine']['danger'] = "Gefahr"; +$lang['quarantine']['qhandler_success'] = "Aktion wurde an das System รผbergeben. Sie dรผrfen dieses Fenster nun schlieรen."; +$lang['warning']['fuzzy_learn_error'] = "Fuzzy Lernfehler: %s"; $lang['danger']['spam_learn_error'] = "Spam Lernfehler: %s"; $lang['success']['qlearn_spam'] = "Nachricht ID %s wurde als Spam gelernt und gelรถscht"; -$lang['header']['quarantine'] = "Quarantรคne"; -$lang['header']['debug'] = "Debugging"; - $lang['debug']['log_info'] = '<p>mailcow <b>in-memory Logs</b> werden in Redis Listen gespeichert, die maximale Anzahl der Eintrรคge pro Anwendung richtet sich nach LOG_LINES (%d). <br>In-memory Logs sind vergรคnglich und nicht zur stรคndigen Aufbewahrung bestimmt. Alle Anwendungen, die in-memory protokollieren, schreiben ebenso in den Docker Daemon. <br>Das in-memory Protokoll versteht sich als schnelle รbersicht zum Debugging eines Containers, fรผr komplexere Protokolle sollte der Docker Daemon konsultiert werden.</p> @@ -613,6 +753,13 @@ $lang['debug']['log_info'] = '<p>mailcow <b>in-memory Logs</b> werden in Redis L $lang['debug']['in_memory_logs'] = 'In-memory Logs'; $lang['debug']['external_logs'] = 'Externe Logs'; $lang['debug']['static_logs'] = 'Statische Logs'; +$lang['debug']['solr_status'] = 'Solr Status'; +$lang['debug']['solr_dead'] = 'Solr startet, ist deaktiviert oder temporรคr nicht erreichbar.'; +$lang['debug']['solr_uptime'] = 'Uptime'; +$lang['debug']['solr_started_at'] = 'Gestartet am'; +$lang['debug']['solr_last_modified'] = 'Zuletzt geรคndert'; +$lang['debug']['solr_size'] = 'Grรถรe'; +$lang['debug']['solr_docs'] = 'Dokumente'; $lang['quarantine']['release_body'] = "Die ursprรผngliche Nachricht wurde als EML-Datei im Anhang hinterlegt."; $lang['danger']['release_send_failed'] = "Die Nachricht konnte nicht versendet werden: %s"; @@ -624,8 +771,8 @@ $lang['mailbox']['bcc_type'] = "BCC Typ"; $lang['mailbox']['bcc_sender_map'] = "Senderabhรคngig"; $lang['mailbox']['bcc_rcpt_map'] = "Empfรคngerabhรคngig"; $lang['mailbox']['bcc_local_dest'] = "Lokales Ziel"; -$lang['mailbox']['bcc_destinations'] = "BCC Ziel"; -$lang['mailbox']['bcc_destination'] = "BCC Ziel"; +$lang['mailbox']['bcc_destinations'] = "BCC-Ziel"; +$lang['mailbox']['bcc_destination'] = "BCC-Ziel"; $lang['edit']['bcc_dest_format'] = 'BCC-Ziel muss eine gรผltige E-Mail-Adresse sein.'; $lang['mailbox']['bcc'] = "BCC"; @@ -645,9 +792,38 @@ $lang['mailbox']['recipient_map_new_info'] = 'Der neue Empfรคnger muss eine E-Ma $lang['mailbox']['recipient_map_old'] = 'Original Empfรคnger'; $lang['mailbox']['recipient_map_new'] = 'Neuer Empfรคnger'; $lang['mailbox']['add_recipient_map_entry'] = 'Empfรคngerumschreibung hinzufรผgen'; -$lang['danger']['invalid_recipient_map_new'] = 'Neuer Empfรคnger %s ist ungรผltig'; -$lang['danger']['invalid_recipient_map_old'] = 'Originaler Empfรคnger %s ist ungรผltig'; -$lang['danger']['recipient_map_entry_exists'] = 'Eine Empfรคngerumschreibung fรผr %s existiert bereits'; -$lang['success']['recipient_map_entry_saved'] = 'Empfรคngerumschreibung fรผr Objekt %s wurde gespeichert'; -$lang['success']['recipient_map_entry_deleted'] = 'Empfรคngerumschreibung fรผr Objekt %s wurde gelรถscht'; +$lang['danger']['invalid_recipient_map_new'] = 'Neuer Empfรคnger "%s" ist ungรผltig'; +$lang['danger']['invalid_recipient_map_old'] = 'Originaler Empfรคnger "%s" ist ungรผltig'; +$lang['danger']['recipient_map_entry_exists'] = 'Eine Empfรคngerumschreibung fรผr Objekt "%s" existiert bereits'; +$lang['success']['recipient_map_entry_saved'] = 'Empfรคngerumschreibung fรผr Objekt "%s" wurde gespeichert'; +$lang['success']['recipient_map_entry_deleted'] = 'Empfรคngerumschreibung mit der ID %s wurde gelรถscht'; +$lang['danger']['tls_policy_map_entry_exists'] = 'Eine TLS-Richtlinie "%s" existiert bereits'; +$lang['success']['tls_policy_map_entry_saved'] = 'TLS-Richtlinieneintrag "%s" wurde gespeichert'; +$lang['success']['tls_policy_map_entry_deleted'] = 'TLS-Richtlinie mit der ID %s wurde gelรถscht'; +$lang['mailbox']['add_tls_policy_map'] = "TLS-Richtlinieneintrag hinzufรผgen"; +$lang['danger']['tls_policy_map_parameter_invalid'] = "Parameter ist ungรผltig"; +$lang['oauth2']['scope_ask_permission'] = 'Eine Anwendung hat um die folgenden Berechtigungen gebeten'; +$lang['oauth2']['profile'] = 'Profil'; +$lang['oauth2']['profile_desc'] = 'Persรถnliche Informationen anzeigen: Benutzername, Name, Erstellzeitpunkt, รnderungszeitpunkt, Status'; +$lang['oauth2']['permit'] = 'Anwendung authorisieren'; +$lang['oauth2']['authorize_app'] = 'Anwendung authorisieren'; +$lang['oauth2']['deny'] = 'Ablehnen'; +$lang['oauth2']['access_denied'] = 'Bitte als Mailbox-Nutzer einloggen, um den Zugriff via OAuth2 zu erlauben.'; + +$lang['admin']['sys_mails'] = 'System-E-Mails'; +$lang['admin']['subject'] = 'Betreff'; +$lang['admin']['from'] = 'Absender'; +$lang['admin']['include_exclude'] = 'Ein- und Ausschlรผsse'; +$lang['admin']['include_exclude_info'] = 'Ohne Auswahl werden alle Mailboxen adressiert.'; +$lang['admin']['excludes'] = 'Diese Empfรคnger ausschlieรen'; +$lang['admin']['includes'] = 'Diese Empfรคnger einschlieรen'; +$lang['admin']['text'] = 'Text'; +$lang['admin']['activate_send'] = 'Senden-Button freischalten'; +$lang['admin']['send'] = 'Senden'; + +$lang['warning']['ip_invalid'] = 'Ungรผltige IP รผbersprungen: %s'; +$lang['danger']['text_empty'] = 'Text darf nicht leer sein'; +$lang['danger']['subject_empty'] = 'Betreff darf nicht leer sein'; +$lang['danger']['from_invalid'] = 'From address must be a valid email address'; +$lang['danger']['network_host_invalid'] = 'Netzwerk oder Host ungรผltig: %s'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 0a04f649..3efe810e 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -3,6 +3,7 @@ * English language file */ +$lang['header']['apps'] = 'Apps'; $lang['footer']['loading'] = "Please wait..."; $lang['header']['restart_sogo'] = 'Restart SOGo'; $lang['header']['restart_netfilter'] = 'Restart netfilter'; @@ -16,6 +17,10 @@ $lang['footer']['delete_these_items'] = 'Please confirm your changes to the foll $lang['footer']['delete_now'] = 'Delete now'; $lang['footer']['cancel'] = 'Cancel'; +$lang['footer']['hibp_nok'] = 'Matched! This is a potentially dangerous password!'; +$lang['footer']['hibp_ok'] = 'No match found.'; + +$lang['danger']['unlimited_quota_acl'] = "Unlimited quota prohibited by ACL"; $lang['danger']['mysql_error'] = "MySQL error: %s"; $lang['danger']['redis_error'] = "Redis error: %s"; $lang['danger']['unknown_tfa_method'] = "Unknown TFA method"; @@ -26,8 +31,14 @@ $lang['success']['verified_u2f_login'] = "Verified U2F login"; $lang['success']['verified_yotp_login'] = "Verified Yubico OTP login"; $lang['danger']['yotp_verification_failed'] = "Yubico OTP verification failed: %s"; $lang['danger']['ip_list_empty'] = "List of allowed IPs cannot be empty"; +$lang['danger']['invalid_destination'] = "Destination format is invalid"; +$lang['danger']['invalid_nexthop'] = "Next hop format is invalid"; +$lang['danger']['invalid_nexthop_authenticated'] = "Next hops exists with different credentials, please update the existing credentials for this next hop first."; +$lang['danger']['next_hop_interferes'] = "%s interferes with nexthop %s"; +$lang['danger']['next_hop_interferes_any'] = "An existing next hop interferes with %s"; $lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI password should be at least 6 chars long"; $lang['success']['rspamd_ui_pw_set'] = "Rspamd UI password successfully set"; +$lang['success']['queue_command_success'] = "Queue command completed successfully"; $lang['danger']['unknown'] = "An unknown error occured"; $lang['danger']['malformed_username'] = "Malformed username"; $lang['info']['awaiting_tfa_confirmation'] = "Awaiting TFA confirmation"; @@ -43,6 +54,7 @@ $lang['danger']['domain_cannot_match_hostname'] = "Domain cannot match hostname" $lang['warning']['domain_added_sogo_failed'] = "Added domain but failed to restart SOGo, please check your server logs."; $lang['danger']['rl_timeframe'] = "Rate limit time frame is incorrect"; $lang['success']['rl_saved'] = "Rate limit for object %s saved"; +$lang['success']['acl_saved'] = "ACL for object %s saved"; $lang['success']['deleted_syncjobs'] = "Deleted syncjobs: %s"; $lang['success']['deleted_syncjob'] = "Deleted syncjob ID %s"; $lang['success']['delete_filters'] = "Deleted filters: %s"; @@ -58,9 +70,9 @@ $lang['danger']['private_key_error'] = "Private key error: %s"; $lang['danger']['map_content_empty'] = "Map content cannot be empty"; $lang['success']['settings_map_added'] = "Added settings map entry"; $lang['danger']['settings_map_invalid'] = "Settings map ID %s invalid"; -$lang['danger']['settings_map_removed'] = "Removed settings map deleted: %s"; +$lang['success']['settings_map_removed'] = "Removed settings map ID %s"; $lang['danger']['invalid_host'] = "Invalid host specified: %s"; -$lang['danger']['relayhost_invalid'] = "Relayhost %s is invalid"; +$lang['danger']['relayhost_invalid'] = "Map entry %s is invalid"; $lang['success']['saved_settings'] = "Saved settings"; $lang['success']['db_init_complete'] = "Database initialization completed"; @@ -72,7 +84,7 @@ $lang['success']['dkim_removed'] = "DKIM key %s has been removed"; $lang['success']['dkim_added'] = "DKIM key %s has been saved"; $lang['success']['dkim_duplicated'] = "DKIM key for domain %s has been copied to %s"; $lang['danger']['access_denied'] = "Access denied or invalid form data"; -$lang['danger']['domain_invalid'] = "Domain name %s is invalid"; +$lang['danger']['domain_invalid'] = "Domain name is empty or invalid"; $lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Max. quota exceeds domain quota limit"; $lang['danger']['object_is_not_numeric'] = "Value %s is not numeric"; $lang['success']['domain_added'] = "Added domain %s"; @@ -104,7 +116,9 @@ $lang['success']['aliasd_modified'] = "Changes to alias domain %s have been save $lang['success']['domain_modified'] = "Changes to domain %s have been saved"; $lang['success']['domain_admin_modified'] = "Changes to domain administrator %s have been saved"; $lang['success']['domain_admin_added'] = "Domain administrator %s has been added"; +$lang['success']['admin_added'] = "Administrator %s has been added"; $lang['success']['admin_modified'] = "Changes to administrator have been saved"; +$lang['success']['admin_api_modified'] = "Changes to API have been saved"; $lang['danger']['username_invalid'] = "Username %s cannot be used"; $lang['danger']['password_mismatch'] = "Confirmation password does not match"; $lang['danger']['password_complexity'] = "Password does not meet the policy"; @@ -128,9 +142,13 @@ $lang['success']['domain_removed'] = "Domain %s has been removed"; $lang['success']['alias_removed'] = "Alias %s has been removed"; $lang['success']['alias_domain_removed'] = "Alias domain %s has been removed"; $lang['success']['domain_admin_removed'] = "Domain administrator %s has been removed"; +$lang['success']['admin_removed'] = "Administrator %s has been removed"; $lang['success']['mailbox_removed'] = "Mailbox %s has been removed"; $lang['success']['eas_reset'] = "ActiveSync devices for user %s were reset"; +$lang['success']['sogo_profile_reset'] = "SOGo profile for user %s was reset"; $lang['success']['resource_removed'] = "Resource %s has been removed"; +$lang['warning']['cannot_delete_self'] = "Cannot delete logged in user"; +$lang['warning']['no_active_admin'] = "Cannot deactivate last active admin"; $lang['danger']['max_quota_in_use'] = "Mailbox quota must be greater or equal to %d MiB"; $lang['danger']['domain_quota_m_in_use'] = "Domain quota must be greater or equal to %s MiB"; $lang['danger']['mailboxes_in_use'] = "Max. mailboxes must be greater or equal to %d"; @@ -158,7 +176,7 @@ $lang['user']['new_password_description'] = 'Requirement: 6 characters long, let $lang['user']['spam_aliases'] = 'Temporary email aliases'; $lang['user']['alias'] = 'Alias'; $lang['user']['shared_aliases'] = 'Shared alias addresses'; -$lang['user']['shared_aliases_desc'] = 'Shared aliases are not affected by user specific settings such as the spam filter or encryption policy. Corresponding spam filters can only be made by an administrator as a domain-wide policy..'; +$lang['user']['shared_aliases_desc'] = 'Shared aliases are not affected by user specific settings such as the spam filter or encryption policy. Corresponding spam filters can only be made by an administrator as a domain-wide policy.'; $lang['user']['direct_aliases'] = 'Direct alias addresses'; $lang['user']['direct_aliases_desc'] = 'Direct alias addresses are affected by spam filter and TLS policy settings.'; $lang['user']['is_catch_all'] = 'Catch-all for domain/s'; @@ -172,11 +190,12 @@ $lang['user']['alias_time_left'] = 'Time left'; $lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; $lang['user']['alias_select_validity'] = 'Period of validity'; $lang['user']['sync_jobs'] = 'Sync jobs'; -$lang['user']['hour'] = 'Hour'; -$lang['user']['hours'] = 'Hours'; -$lang['user']['day'] = 'Day'; -$lang['user']['week'] = 'Week'; -$lang['user']['weeks'] = 'Weeks'; +$lang['user']['expire_in'] = 'Expire in'; +$lang['user']['hour'] = 'hour'; +$lang['user']['hours'] = 'hours'; +$lang['user']['day'] = 'day'; +$lang['user']['week'] = 'week'; +$lang['user']['weeks'] = 'weeks'; $lang['user']['spamfilter'] = 'Spam filter'; $lang['admin']['spamfilter'] = 'Spam filter'; $lang['user']['spamfilter_wl'] = 'Whitelist'; @@ -213,10 +232,15 @@ $lang['user']['tag_in_none'] = 'Do nothing'; $lang['user']['tag_help_explain'] = 'In subfolder: a new subfolder named after the tag will be created below INBOX ("INBOX/Facebook").<br> In subject: the tags name will be prepended to the mails subject, example: "[Facebook] My News".'; $lang['user']['tag_help_example'] = 'Example for a tagged email address: me<b>+Facebook</b>@example.org'; + $lang['user']['eas_reset'] = 'Reset ActiveSync device cache'; $lang['user']['eas_reset_now'] = 'Reset now'; $lang['user']['eas_reset_help'] = 'In many cases a device cache reset will help to recover a broken ActiveSync profile.<br><b>Attention:</b> All elements will be redownloaded!'; +$lang['user']['sogo_profile_reset'] = 'Reset SOGo profile'; +$lang['user']['sogo_profile_reset_now'] = 'Reset profile now'; +$lang['user']['sogo_profile_reset_help'] = 'This will destroy a users SOGo profile and <b>delete all data irretrievable</b>.'; + $lang['user']['encryption'] = 'Encryption'; $lang['user']['username'] = 'Username'; $lang['user']['last_run'] = 'Last run'; @@ -230,14 +254,28 @@ $lang['user']['create_syncjob'] = 'Create new sync job'; $lang['start']['mailcow_apps_detail'] = 'Use a mailcow app to access your mails, calendar, contacts and more.'; $lang['start']['mailcow_panel_detail'] = '<b>Domain administrators</b> create, modify or delete mailboxes and aliases, change domains and read further information about their assigned domains.<br> - <b>Mailbox users</b> are able to create time-limited aliases (spam aliases), change their password and spam filter settings.'; +<b>Mailbox users</b> are able to create time-limited aliases (spam aliases), change their password and spam filter settings.'; $lang['start']['imap_smtp_server_auth_info'] = 'Please use your full email address and the PLAIN authentication mechanism.<br> Your login data will be encrypted by the server-side mandatory encryption.'; $lang['start']['help'] = 'Show/Hide help panel'; $lang['header']['mailcow_settings'] = 'Configuration'; -$lang['header']['administration'] = 'Administration'; -$lang['header']['mailboxes'] = 'Mailboxes'; -$lang['header']['user_settings'] = 'User settings'; +$lang['header']['administration'] = 'Configuration & Details'; +$lang['header']['mailboxes'] = 'Mail Setup'; +$lang['header']['user_settings'] = 'User Settings'; +$lang['header']['quarantine'] = "Quarantine"; +$lang['header']['debug'] = "System Information"; +$lang['quarantine']['disabled_by_config'] = "The current system configuration disables the quarantine functionality."; +$lang['mailbox']['tls_policy_maps'] = 'TLS policy maps'; +$lang['mailbox']['tls_policy_maps_long'] = 'Outgoing TLS policy map overrides'; +$lang['mailbox']['tls_policy_maps_info'] = 'This policy map overrides outgoing TLS transport rules independently of a users TLS policy settings.<br> + Please check <a href="http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps" target="_blank">the "smtp_tls_policy_maps" docs</a> for further information.'; +$lang['mailbox']['tls_enforce_in'] = 'Enforce TLS incoming'; +$lang['mailbox']['tls_enforce_out'] = 'Enforce TLS outgoing'; +$lang['mailbox']['tls_map_dest'] = 'Destination'; +$lang['mailbox']['tls_map_dest_info'] = 'Examples: example.org, .example.org, mail@example.org, [mail.example.org]:25'; +$lang['mailbox']['tls_map_policy'] = 'Policy'; +$lang['mailbox']['tls_map_parameters'] = 'Parameters'; +$lang['mailbox']['tls_map_parameters_info'] = 'Empty or parameters, for example: protocols=!SSLv2 ciphers=medium exclude=3DES'; $lang['mailbox']['booking_0'] = 'Always show as free'; $lang['mailbox']['booking_lt0'] = 'Unlimited, but show as busy when booked'; $lang['mailbox']['booking_custom'] = 'Hard-limit to a custom amount of bookings'; @@ -252,8 +290,10 @@ $lang['mailbox']['description'] = 'Description'; $lang['mailbox']['alias'] = 'Alias'; $lang['mailbox']['aliases'] = 'Aliases'; $lang['mailbox']['domains'] = 'Domains'; +$lang['admin']['domain'] = 'Domain'; $lang['admin']['domain_s'] = 'Domain/s'; $lang['mailbox']['mailboxes'] = 'Mailboxes'; +$lang['mailbox']['mailbox'] = 'Mailbox'; $lang['mailbox']['resources'] = 'Resources'; $lang['mailbox']['mailbox_quota'] = 'Max. size of a mailbox'; $lang['mailbox']['domain_quota'] = 'Quota'; @@ -266,8 +306,8 @@ $lang['mailbox']['target_address'] = 'Goto address'; $lang['mailbox']['username'] = 'Username'; $lang['mailbox']['fname'] = 'Full name'; $lang['mailbox']['filter_table'] = 'Filter table'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; +$lang['mailbox']['yes'] = '✓'; +$lang['mailbox']['no'] = '✕'; $lang['mailbox']['in_use'] = 'In use (%)'; $lang['mailbox']['msg_num'] = 'Message #'; $lang['mailbox']['remove'] = 'Remove'; @@ -319,8 +359,12 @@ $lang['edit']['max_mailboxes'] = 'Max. possible mailboxes'; $lang['edit']['title'] = 'Edit object'; $lang['edit']['target_address'] = 'Goto address/es <small>(comma-separated)</small>'; $lang['edit']['active'] = 'Active'; +$lang['edit']['gal'] = 'Global Address List'; +$lang['edit']['gal_info'] = 'The GAL contains all objects of a domain and cannot be edited by any user. <b>Restart SOGo to apply changes.</b>'; $lang['edit']['force_pw_update'] = 'Force password update at next login'; $lang['edit']['force_pw_update_info'] = 'This user will only be able to login to mailcow UI.'; +$lang['edit']['sogo_access'] = 'Grant access to SOGo'; +$lang['edit']['sogo_access_info'] = 'Grant or permit access to SOGo. This setting does neither affect access to all other services nor does it delete or change a users existing SOGo profile.'; $lang['edit']['target_domain'] = 'Target domain'; $lang['edit']['password'] = 'Password'; $lang['edit']['password_repeat'] = 'Confirmation password (repeat)'; @@ -342,16 +386,61 @@ $lang['edit']['full_name'] = 'Full name'; $lang['edit']['quota_mb'] = 'Quota (MiB)'; $lang['edit']['sender_acl'] = 'Allow to send as'; $lang['edit']['sender_acl_disabled'] = 'โณ <span class="label label-danger">Sender check is disabled</span>'; +$lang['user']['sender_acl_disabled'] = '<span class="label label-danger">Sender check is disabled</span>'; $lang['edit']['previous'] = 'Previous page'; $lang['edit']['unchanged_if_empty'] = 'If unchanged leave blank'; $lang['edit']['dont_check_sender_acl'] = "Disable sender check for domain %s (+ alias domains)"; $lang['edit']['multiple_bookings'] = 'Multiple bookings'; $lang['edit']['kind'] = 'Kind'; $lang['edit']['resource'] = 'Resource'; +$lang['edit']['relayhost'] = 'Sender-dependent transports'; +$lang['edit']['public_comment'] = 'Public comment'; +$lang['mailbox']['public_comment'] = 'Public comment'; +$lang['edit']['private_comment'] = 'Private comment'; +$lang['mailbox']['private_comment'] = 'Private comment'; +$lang['edit']['comment_info'] = 'A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a users overview'; +$lang['add']['public_comment'] = 'Public comment'; +$lang['add']['private_comment'] = 'Private comment'; +$lang['add']['comment_info'] = 'A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a users overview'; +$lang['acl']['spam_alias'] = 'Temporary aliases'; +$lang['acl']['tls_policy'] = 'TLS policy'; +$lang['acl']['spam_score'] = 'Spam score'; +$lang['acl']['spam_policy'] = 'Blacklist/Whitelist'; +$lang['acl']['delimiter_action'] = 'Delimiter action'; +$lang['acl']['syncjobs'] = 'Sync jobs'; +$lang['acl']['eas_reset'] = 'Reset EAS devices'; +$lang['acl']['sogo_profile_reset'] = 'Reset SOGo profile'; +$lang['acl']['quarantine'] = 'Quarantine actions'; +$lang['acl']['quarantine_notification'] = 'Quarantine notifications'; +$lang['acl']['quarantine_attachments'] = 'Quarantine attachments'; +$lang['acl']['alias_domains'] = 'Add alias domains'; +$lang['acl']['login_as'] = 'Login as mailbox user'; +$lang['acl']['bcc_maps'] = 'BCC maps'; +$lang['acl']['filters'] = 'Filters'; +$lang['acl']['ratelimit'] = 'Rate limit'; +$lang['acl']['recipient_maps'] = 'Recipient maps'; +$lang['acl']['unlimited_quota'] = 'Unlimited quota for mailboxes'; +$lang['acl']['prohibited'] = 'Prohibited by ACL'; +$lang['mailbox']['quarantine_notification'] = 'Quarantine notifications'; +$lang['mailbox']['never'] = 'Never'; +$lang['mailbox']['hourly'] = 'Hourly'; +$lang['mailbox']['daily'] = 'Daily'; +$lang['mailbox']['weekly'] = 'Weekly'; +$lang['user']['quarantine_notification'] = 'Quarantine notifications'; +$lang['user']['never'] = 'Never'; +$lang['user']['hourly'] = 'Hourly'; +$lang['user']['daily'] = 'Daily'; +$lang['user']['weekly'] = 'Weekly'; +$lang['user']['quarantine_notification_info'] = 'Once a notification has been sent, items will be marked as "notified" and no further notifications will be sent for this particular item.'; + +$lang['add']['generate'] = 'generate'; $lang['add']['syncjob'] = 'Add sync job'; $lang['add']['syncjob_hint'] = 'Be aware that passwords need to be saved plain-text!'; -$lang['add']['hostname'] = 'Hostname'; +$lang['add']['hostname'] = 'Host'; +$lang['add']['destination'] = 'Destination'; +$lang['add']['nexthop'] = 'Next hop'; +$lang['edit']['nexthop'] = 'Next hop'; $lang['add']['port'] = 'Port'; $lang['add']['username'] = 'Username'; $lang['add']['enc_method'] = 'Encryption method'; @@ -364,6 +453,8 @@ $lang['add']['custom_params'] = 'Custom parameters'; $lang['add']['subscribeall'] = 'Subscribe all folders'; $lang['add']['timeout1'] = 'Timeout for connection to remote host'; $lang['add']['timeout2'] = 'Timeout for connection to local host'; +$lang['edit']['timeout1'] = 'Timeout for connection to remote host'; +$lang['edit']['timeout2'] = 'Timeout for connection to local host'; $lang['edit']['delete2duplicates'] = 'Delete duplicates on destination'; $lang['edit']['delete1'] = 'Delete from source when completed'; @@ -445,6 +536,7 @@ $lang['admin']['rspamd-com_settings'] = '<a href="https://rspamd.com/doc/configu - A setting name will be auto-generated, please see the example presets below.'; $lang['admin']['no_new_rows'] = 'No further rows available'; +$lang['admin']['queue_manager'] = 'Queue manager'; $lang['admin']['additional_rows'] = ' additional rows were added'; // parses to 'n additional rows were added' $lang['admin']['private_key'] = 'Private key'; $lang['admin']['import'] = 'Import'; @@ -474,7 +566,9 @@ $lang['admin']['dkim_key_unused'] = 'Key unused'; $lang['admin']['dkim_key_missing'] = 'Key missing'; $lang['admin']['dkim_add_key'] = 'Add ARC/DKIM key'; $lang['admin']['dkim_keys'] = 'ARC/DKIM keys'; +$lang['admin']['dkim_private_key'] = 'Private key'; $lang['admin']['dkim_domains_wo_keys'] = "Select domains with missing keys"; +$lang['admin']['dkim_domains_selector'] = "Selector"; $lang['admin']['add'] = 'Add'; $lang['add']['add_domain_restart'] = 'Add domain and restart SOGo'; $lang['add']['add_domain_only'] = 'Add domain only'; @@ -485,6 +579,7 @@ $lang['admin']['active'] = 'Active'; $lang['admin']['inactive'] = 'Inactive'; $lang['admin']['action'] = 'Action'; $lang['admin']['add_domain_admin'] = 'Add domain administrator'; +$lang['admin']['add_admin'] = 'Add administrator'; $lang['admin']['add_settings_rule'] = 'Add settings rule'; $lang['admin']['rsetting_desc'] = 'Short description'; $lang['admin']['rsetting_content'] = 'Rule content'; @@ -494,8 +589,18 @@ $lang['admin']['rsettings_preset_1'] = 'Disable all but DKIM and rate limit for $lang['admin']['rsettings_preset_2'] = 'Postmasters want spam'; $lang['admin']['rsettings_insert_preset'] = 'Insert example preset "%s"'; $lang['admin']['rsetting_add_rule'] = 'Add rule'; +$lang['admin']['queue_ays'] = 'Please confirm you want to delete all items from the current queue.'; +$lang['admin']['arrival_time'] = 'Arrival time (server time)'; +$lang['admin']['message_size'] = 'Message size'; +$lang['admin']['sender'] = 'Sender'; +$lang['admin']['recipients'] = 'Recipients'; $lang['admin']['admin_domains'] = 'Domain assignments'; $lang['admin']['domain_admins'] = 'Domain administrators'; +$lang['admin']['flush_queue'] = 'Flush queue'; +$lang['admin']['delete_queue'] = 'Delete all'; +$lang['admin']['queue_deliver_mail'] = 'Deliver'; +$lang['admin']['queue_hold_mail'] = 'Hold'; +$lang['admin']['queue_unhold_mail'] = 'Unhold'; $lang['admin']['username'] = 'Username'; $lang['admin']['edit'] = 'Edit'; $lang['admin']['remove'] = 'Remove'; @@ -503,31 +608,55 @@ $lang['admin']['save'] = 'Save changes'; $lang['admin']['admin'] = 'Administrator'; $lang['admin']['admin_details'] = 'Edit administrator details'; $lang['admin']['unchanged_if_empty'] = 'If unchanged leave blank'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; +$lang['admin']['yes'] = '✓'; +$lang['admin']['no'] = '✕'; $lang['admin']['access'] = 'Access'; $lang['admin']['no_record'] = 'No record'; $lang['admin']['filter_table'] = 'Filter table'; $lang['admin']['empty'] = 'No results'; $lang['admin']['time'] = 'Time'; +$lang['admin']['last_applied'] = 'Last applied'; +$lang['admin']['reset_limit'] = 'Remove hash'; +$lang['admin']['hash_remove_info'] = 'Removing a ratelimit hash (if still existing) will reset its counter completely.<br> + Each hash is indicated by an individual color.'; +$lang['warning']['hash_not_found'] = 'Hash not found'; +$lang['success']['hash_deleted'] = 'Hash deleted'; +$lang['admin']['authed_user'] = 'Auth. user'; $lang['admin']['priority'] = 'Priority'; $lang['admin']['message'] = 'Message'; +$lang['admin']['rate_name'] = 'Rate name'; $lang['admin']['refresh'] = 'Refresh'; $lang['admin']['to_top'] = 'Back to top'; $lang['admin']['in_use_by'] = 'In use by'; $lang['admin']['forwarding_hosts'] = 'Forwarding Hosts'; $lang['admin']['forwarding_hosts_hint'] = 'Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.'; $lang['admin']['forwarding_hosts_add_hint'] = 'You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).'; -$lang['admin']['relayhosts_hint'] = 'Define relayhosts here to be able to select them in a domains configuration dialog.'; -$lang['admin']['add_relayhost_add_hint'] = 'Please be aware that relayhost authentication data will be stored as plain text.'; +$lang['admin']['relayhosts_hint'] = 'Define sender-dependent transports to be able to select them in a domains configuration dialog.<br> + The transport service is always "smtp:". A users individual outbound TLS policy setting is taken into account.'; +$lang['admin']['transports_hint'] = 'โ A transport map entry <b>overrules</b> a sender-dependent transport map</b>.<br> +โ Outbound TLS policy settings per-user are ignored and can only be enfored by TLS policy map entries.<br> +โ The transport service for defined transports is always "smtp:".<br> +โ Adresses matching "/localhost$/" will always be transported via "local:", therefore a "*" destination will not apply to those addresses.<br> +โ To determine credentials for an exemplary next hop "[host]:25", Postfix <b>always</b> queries for "host" before searching for "[host]:25". This behavior makes it impossible to use "host" and "[host]:25" at the same time.'; +$lang['admin']['add_relayhost_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.'; +$lang['admin']['add_transports_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.'; $lang['admin']['host'] = 'Host'; $lang['admin']['source'] = 'Source'; -$lang['admin']['add_forwarding_host'] = 'Add Forwarding Host'; -$lang['admin']['add_relayhost'] = 'Add Relayhost'; +$lang['admin']['add_forwarding_host'] = 'Add forwarding host'; +$lang['admin']['add_relayhost'] = 'Add sender-dependent transport'; +$lang['admin']['add_transport'] = 'Add transport'; +$lang['admin']['relayhosts'] = 'Sender-dependent transports'; +$lang['admin']['transport_maps'] = 'Transport Maps'; +$lang['admin']['routing'] = 'Routing'; +$lang['admin']['credentials_transport_warning'] = '<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching nexthop column.'; + +$lang['admin']['destination'] = 'Destination'; +$lang['admin']['nexthop'] = 'Next hop'; + $lang['success']['forwarding_host_removed'] = "Forwarding host %s has been removed"; $lang['success']['forwarding_host_added'] = "Forwarding host %s has been added"; -$lang['success']['relayhost_removed'] = "Relayhost %s has been removed"; -$lang['success']['relayhost_added'] = "Relayhost %s has been added"; +$lang['success']['relayhost_removed'] = "Map entry %s has been removed"; +$lang['success']['relayhost_added'] = "Map entry %s has been added"; $lang['diagnostics']['dns_records'] = 'DNS Records'; $lang['diagnostics']['dns_records_24hours'] = 'Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.'; $lang['diagnostics']['dns_records_name'] = 'Name'; @@ -539,7 +668,7 @@ $lang['diagnostics']['cname_from_a'] = 'Value derived from A/AAAA record. This i $lang['admin']['relay_from'] = '"From:" address'; $lang['admin']['relay_run'] = "Run test"; -$lang['admin']['api_allow_from'] = "Allow API access from these IPs"; +$lang['admin']['api_allow_from'] = "Allow API access from these IPs (separated by comma or new line)"; $lang['admin']['api_key'] = "API key"; $lang['admin']['activate_api'] = "Activate API"; $lang['admin']['regen_api_key'] = "Regenerate API key"; @@ -549,10 +678,23 @@ $lang['admin']['queue_unban'] = "queue unban"; $lang['admin']['no_active_bans'] = "No active bans"; $lang['admin']['quarantine'] = "Quarantine"; -$lang['admin']['quarantine_retention_size'] = "Retentions per mailbox<br />0 indicates <b>inactive</b>!"; -$lang['admin']['quarantine_max_size'] = "Maximum size in MiB (larger elements are discarded)<br />0 does <b>not</b> indicate unlimited!"; -$lang['admin']['quarantine_exclude_domains'] = "Exclude domains and alias-domains:"; - +$lang['admin']['rspamd_settings_map'] = "Rspamd settings map"; +$lang['admin']['quota_notifications'] = "Quota notifications"; +$lang['admin']['quota_notifications_vars'] = "{{percent}} equals the current quota of the user<br>{{username}} is the mailbox name"; +$lang['admin']['active_rspamd_settings_map'] = "Active settings map"; +$lang['admin']['quota_notifications_info'] = "Quota notications are sent to users once when crossing 80% and once when crossing 95% usage."; +$lang['admin']['quarantine_retention_size'] = "Retentions per mailbox:<br><small>0 indicates <b>inactive</b>.</small>"; +$lang['admin']['quarantine_max_size'] = "Maximum size in MiB (larger elements are discarded):<br><small>0 does <b>not</b> indicate unlimited.</small>"; +$lang['admin']['quarantine_exclude_domains'] = "Exclude domains and alias-domains"; +$lang['admin']['quarantine_release_format'] = "Format of released items"; +$lang['admin']['quarantine_release_format_raw'] = "Unmodified original"; +$lang['admin']['quarantine_release_format_att'] = "As attachment"; +$lang['admin']['quarantine_notification_sender'] = "Notification email sender"; +$lang['admin']['quarantine_notification_subject'] = "Notification email subject"; +$lang['admin']['quarantine_notification_html'] = "Notification email template:<br><small>Leave empty to restore default template.</small>"; +$lang['admin']['quota_notification_sender'] = "Notification email sender"; +$lang['admin']['quota_notification_subject'] = "Notification email subject"; +$lang['admin']['quota_notification_html'] = "Notification email template:<br><small>Leave empty to restore default template.</small>"; $lang['admin']['ui_texts'] = "UI labels and texts"; $lang['admin']['help_text'] = "Override help text below login mask (HTML allowed)"; $lang['admin']['title_name'] = '"mailcow UI" website title'; @@ -573,11 +715,15 @@ $lang['admin']['merged_vars_hint'] = 'Greyed out rows were merged from <code>var $lang['mailbox']['waiting'] = "Waiting"; $lang['mailbox']['status'] = "Status"; $lang['mailbox']['running'] = "Running"; +$lang['mailbox']['enable_x'] = "Enable"; +$lang['mailbox']['disable_x'] = "Disable"; $lang['edit']['spam_score'] = "Set a custom spam score"; +$lang['user']['spam_score_reset'] = "Reset to server default"; $lang['edit']['spam_policy'] = "Add or remove items to white-/blacklist"; $lang['edit']['spam_alias'] = "Create or change time limited alias addresses"; +$lang['danger']['comment_too_long'] = "Comment too long, max 160 chars allowed"; $lang['danger']['img_tmp_missing'] = "Cannot validate image file: Temporary file not found"; $lang['danger']['img_invalid'] = "Cannot validate image file"; $lang['danger']['invalid_mime_type'] = "Invalid mime type"; @@ -610,13 +756,22 @@ $lang['quarantine']['subj'] = "Subject"; $lang['quarantine']['text_plain_content'] = "Content (text/plain)"; $lang['quarantine']['text_from_html_content'] = "Content (converted html)"; $lang['quarantine']['atts'] = "Attachments"; -$lang['danger']['fuzzy_learn_error'] = "Fuzzy hash learn error: %s"; +$lang['quarantine']['low_danger'] = "Low danger"; +$lang['quarantine']['neutral_danger'] = "Neutral/no rating"; +$lang['quarantine']['medium_danger'] = "Medium danger"; +$lang['quarantine']['high_danger'] = "High"; +$lang['quarantine']['danger'] = "Danger"; +$lang['quarantine']['confirm_delete'] = "Confirm the deletion of this element."; +$lang['quarantine']['qhandler_success'] = "Request successfully sent to the system. You can now close the window."; + +$lang['warning']['fuzzy_learn_error'] = "Fuzzy hash learn error: %s"; $lang['danger']['spam_learn_error'] = "Spam learn error: %s"; $lang['success']['qlearn_spam'] = "Message ID %s was learned as spam and deleted"; -$lang['header']['quarantine'] = "Quarantine"; -$lang['header']['debug'] = "Debug"; - +$lang['debug']['system_containers'] = 'System & Containers'; +$lang['debug']['solr_status'] = 'Solr status'; +$lang['debug']['solr_dead'] = 'Solr is starting, disabled or died.'; +$lang['debug']['logs'] = 'Logs'; $lang['debug']['log_info'] = '<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering. <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver. <br>The in-memory log type should be used for debugging minor issues with containers.</p> @@ -626,6 +781,15 @@ $lang['debug']['log_info'] = '<p>mailcow <b>in-memory logs</b> are collected in $lang['debug']['in_memory_logs'] = 'In-memory logs'; $lang['debug']['external_logs'] = 'External logs'; $lang['debug']['static_logs'] = 'Static logs'; +$lang['debug']['solr_uptime'] = 'Uptime'; +$lang['debug']['solr_started_at'] = 'Started at'; +$lang['debug']['solr_last_modified'] = 'Last modified'; +$lang['debug']['solr_size'] = 'Size'; +$lang['debug']['solr_docs'] = 'Docs'; + +$lang['debug']['disk_usage'] = 'Disk usage'; +$lang['debug']['containers_info'] = "Container information"; +$lang['debug']['restart_container'] = 'Restart'; $lang['quarantine']['release_body'] = "We have attached your message as eml file to this message."; $lang['danger']['release_send_failed'] = "Message could not be released: %s"; @@ -646,6 +810,7 @@ $lang['mailbox']['bcc_maps'] = "BCC maps"; $lang['mailbox']['bcc_to_sender'] = "Switch to sender map type"; $lang['mailbox']['bcc_to_rcpt'] = "Switch to recipient map type"; $lang['mailbox']['add_bcc_entry'] = "Add BCC map"; +$lang['mailbox']['add_tls_policy_map'] = "Add TLS policy map"; $lang['mailbox']['bcc_info'] = "BCC maps are used to silently forward copies of all messages to another address. A recipient map type entry is used, when the local destination acts as recipient of a mail. Sender maps conform to the same principle.<br/> The local destination will not be informed about a failed delivery."; $lang['mailbox']['address_rewriting'] = 'Address rewriting'; @@ -658,10 +823,14 @@ $lang['mailbox']['recipient_map_old'] = 'Original recipient'; $lang['mailbox']['recipient_map_new'] = 'New recipient'; $lang['danger']['invalid_recipient_map_new'] = 'Invalid new recipient specified: %s'; $lang['danger']['invalid_recipient_map_old'] = 'Invalid original recipient specified: %s'; -$lang['danger']['recipient_map_entry_exists'] = 'A Recipient map entry for %s exists'; -$lang['success']['recipient_map_entry_saved'] = 'Recipient map entry for %s has been saved'; -$lang['success']['recipient_map_entry_deleted'] = 'Recipient map entry for %s has been deleted'; +$lang['danger']['recipient_map_entry_exists'] = 'A Recipient map entry "%s" exists'; +$lang['success']['recipient_map_entry_saved'] = 'Recipient map entry "%s" has been saved'; +$lang['success']['recipient_map_entry_deleted'] = 'Recipient map ID %s has been deleted'; +$lang['danger']['tls_policy_map_entry_exists'] = 'A TLS policy map entry "%s" exists'; +$lang['success']['tls_policy_map_entry_saved'] = 'TLS policy map entry "%s" has been saved'; +$lang['success']['tls_policy_map_entry_deleted'] = 'TLS policy map ID %s has been deleted'; $lang['mailbox']['add_recipient_map_entry'] = 'Add recipient map'; +$lang['danger']['tls_policy_map_parameter_invalid'] = "Policy parameter is invalid"; $lang['oauth2']['scope_ask_permission'] = 'An application asked for the following permissions'; $lang['oauth2']['profile'] = 'Profile'; @@ -670,3 +839,20 @@ $lang['oauth2']['permit'] = 'Authorize application'; $lang['oauth2']['authorize_app'] = 'Authorize application'; $lang['oauth2']['deny'] = 'Deny'; $lang['oauth2']['access_denied'] = 'Please login as mailbox owner to grant access via OAuth2.'; + +$lang['admin']['sys_mails'] = 'System mails'; +$lang['admin']['subject'] = 'Subject'; +$lang['admin']['from'] = 'From'; +$lang['admin']['include_exclude'] = 'Include/Exclude'; +$lang['admin']['include_exclude_info'] = 'By default - with no selection - <b>all mailboxes</b> are addressed'; +$lang['admin']['excludes'] = 'Excludes these recipients'; +$lang['admin']['includes'] = 'Include these recipients'; +$lang['admin']['text'] = 'Text'; +$lang['admin']['activate_send'] = 'Activate send button'; +$lang['admin']['send'] = 'Send'; + +$lang['warning']['ip_invalid'] = 'Skipped invalid IP: %s'; +$lang['danger']['text_empty'] = 'Text must not be empty'; +$lang['danger']['subject_empty'] = 'Subject must not be empty'; +$lang['danger']['from_invalid'] = 'Sender must not be empty'; +$lang['danger']['network_host_invalid'] = 'Invalid network or host: %s'; diff --git a/data/web/lang/lang.es.php b/data/web/lang/lang.es.php index 570cea20..0d0e033b 100644 --- a/data/web/lang/lang.es.php +++ b/data/web/lang/lang.es.php @@ -153,8 +153,6 @@ $lang['mailbox']['target_address'] = 'Direcciรณn Goto'; $lang['mailbox']['username'] = 'Nombre de usuario'; $lang['mailbox']['fname'] = 'Nombre completo'; $lang['mailbox']['filter_table'] = 'Filtrar tabla'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'En uso (%)'; $lang['mailbox']['msg_num'] = 'Mensaje #'; $lang['mailbox']['remove'] = 'Eliminar'; @@ -259,7 +257,5 @@ $lang['admin']['save'] = 'Guardar cambios'; $lang['admin']['admin'] = 'Administrador'; $lang['admin']['admin_details'] = 'Editar detalles del administrador'; $lang['admin']['unchanged_if_empty'] = 'Si no hay cambios dejalo en blanco'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'Acceso'; $lang['admin']['no_record'] = 'Sin registro'; diff --git a/data/web/lang/lang.fr.php b/data/web/lang/lang.fr.php index a67a447f..48b51ae4 100644 --- a/data/web/lang/lang.fr.php +++ b/data/web/lang/lang.fr.php @@ -132,7 +132,7 @@ $lang['user']['spamfilter_red'] = "Rouge: Ce message est un pourriel et sera rej $lang['user']['spamfilter_hint'] = "La premiรจre valeur dรฉcrit le \"score bas de pourriel\", la seconde reprรฉsente le \"score haut de pourriel\"."; $lang['user']['spamfilter_table_domain_policy'] = "N/D (Politique du domaine)"; -$lang['user']['tls_policy_warning'] = "<strong>Attention :<strong> Si vous dรฉcidez d'imposer le chiffrement des รฉchanges de courriel, vous pouvez perdre des messages.<br> Les messages qui ne respecte pas la politique seront rejetรฉs avec un message d'erreur dรฉfinitif par le systรจme de courriel.<br> Cette option s'applique ร votre adresse de courriel principale (identifiant de connexion), tous les alias de domaine ainsi que les alias d'adresse <b> qui n'ont que cette unique boรฎte</b> comme destinataire."; +$lang['user']['tls_policy_warning'] = "<strong>Attention :</strong> Si vous dรฉcidez d'imposer le chiffrement des รฉchanges de courriel, vous pouvez perdre des messages.<br> Les messages qui ne respectent pas la politique seront rejetรฉs avec un message d'erreur dรฉfinitif par le systรจme de courriel.<br> Cette option s'applique ร votre adresse de courriel principale (identifiant de connexion), tous les alias de domaine ainsi que les alias d'adresse <b> qui n'ont que cette unique boรฎte</b> comme destinataire."; $lang['user']['tls_policy'] = "Politique de chiffrement"; $lang['user']['tls_enforce_in'] = "Imposer le TLS entrant"; $lang['user']['tls_enforce_out'] = "Imposer le TLS sortant"; @@ -188,8 +188,6 @@ $lang['mailbox']['target_address'] = "Adresse cible"; $lang['mailbox']['username'] = "Identifiant"; $lang['mailbox']['fname'] = "Nom complet"; $lang['mailbox']['filter_table'] = "Table de filtrage"; -$lang['mailbox']['yes'] = "✔"; -$lang['mailbox']['no'] = "✘"; $lang['mailbox']['in_use'] = "Utilisation (%)"; $lang['mailbox']['msg_num'] = "Message"; $lang['mailbox']['remove'] = "Retirer"; @@ -358,8 +356,6 @@ $lang['admin']['remove'] = "Retirer"; $lang['admin']['admin'] = "Administrateur"; $lang['admin']['admin_details'] = "รditer les informations de l'administrateur"; $lang['admin']['unchanged_if_empty'] = "Si aucun changement, laisser vide"; -$lang['admin']['yes'] = "✔"; -$lang['admin']['no'] = "✘"; $lang['admin']['access'] = "Accรจs"; $lang['admin']['no_record'] = "Aucun enregistrement"; $lang['admin']['filter_table'] = "Table de filtrage"; diff --git a/data/web/lang/lang.it.php b/data/web/lang/lang.it.php index da355609..b79fcb59 100644 --- a/data/web/lang/lang.it.php +++ b/data/web/lang/lang.it.php @@ -191,8 +191,6 @@ $lang['mailbox']['target_address'] = 'Vai ad indirizzo'; $lang['mailbox']['username'] = 'Nome utente'; $lang['mailbox']['fname'] = 'Nome completo'; $lang['mailbox']['filter_table'] = 'Filra tabella'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'In uso (%)'; $lang['mailbox']['msg_num'] = 'Messaggio #'; $lang['mailbox']['remove'] = 'Rimuovi'; @@ -348,8 +346,6 @@ $lang['admin']['save'] = 'Salva modifiche'; $lang['admin']['admin'] = 'Amministratore'; $lang['admin']['admin_details'] = 'Modifica impostazioni amministratore'; $lang['admin']['unchanged_if_empty'] = 'Se immutato lasciare vuoto'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'Accedi'; $lang['admin']['no_record'] = 'Nessun risultato'; $lang['admin']['filter_table'] = 'Tabella filtro'; diff --git a/data/web/lang/lang.lv.php b/data/web/lang/lang.lv.php index f612607b..2fe3290e 100644 --- a/data/web/lang/lang.lv.php +++ b/data/web/lang/lang.lv.php @@ -206,8 +206,6 @@ $lang['mailbox']['target_address'] = 'Doties uz adresi'; $lang['mailbox']['username'] = 'Lietotฤjvฤrds'; $lang['mailbox']['fname'] = 'Pilns vฤrds'; $lang['mailbox']['filter_table'] = 'Filtra tabula'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'Lietoลกanฤ (%)'; $lang['mailbox']['msg_num'] = 'Vฤstule #'; $lang['mailbox']['remove'] = 'Noลemt'; @@ -404,8 +402,6 @@ $lang['admin']['save'] = 'Saglabฤt izmaiลas'; $lang['admin']['admin'] = 'Administrators'; $lang['admin']['admin_details'] = 'Labot administratora detaฤผas'; $lang['admin']['unchanged_if_empty'] = 'Ja nav veiktas izmaiลas, atstฤjiet tukลกu'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'Pieeja'; $lang['admin']['no_record'] = 'Nav ierakstu'; $lang['admin']['filter_table'] = 'Filtru tabula'; diff --git a/data/web/lang/lang.nl.php b/data/web/lang/lang.nl.php index 385c0c04..0c73ee99 100644 --- a/data/web/lang/lang.nl.php +++ b/data/web/lang/lang.nl.php @@ -1,8 +1,12 @@ <?php /* * Dutch language file + * + * Created and maintained by Geitenijs + * */ +$lang['header']['apps'] = 'Apps'; $lang['footer']['loading'] = "Even geduld aub..."; $lang['header']['restart_sogo'] = 'Herstart SOGo'; $lang['header']['restart_netfilter'] = 'Herstart netfilter'; @@ -16,6 +20,9 @@ $lang['footer']['delete_these_items'] = 'Bevestig de wijzigingen aan het volgend $lang['footer']['delete_now'] = 'Nu verwijderen'; $lang['footer']['cancel'] = 'Annuleren'; +$lang['footer']['hibp_nok'] = 'Dit is een potentieel onveilig wachtwoord!'; +$lang['footer']['hibp_ok'] = 'Dit wachtwoord is relatief veilig'; + $lang['danger']['mysql_error'] = "MySQL-fout: %s"; $lang['danger']['redis_error'] = "Redis-fout: %s"; $lang['danger']['unknown_tfa_method'] = "Onbekende tweefactorauthenticatiemethode"; @@ -25,41 +32,49 @@ $lang['danger']['u2f_verification_failed'] = "U2F-verificatie mislukt: %s"; $lang['success']['verified_u2f_login'] = "U2F succesvol geverifieerd"; $lang['success']['verified_yotp_login'] = "Yubico OTP succesvol geverifieerd"; $lang['danger']['yotp_verification_failed'] = "Yubico OTP-verificatie mislukt: %s"; -$lang['danger']['ip_list_empty'] = "Lijst met toegestane IP-adressen kan niet leeg zijn"; +$lang['danger']['ip_list_empty'] = "Lijst met toegestane IP-adressen dient ingevuld te worden"; +$lang['danger']['invalid_destination'] = "Formaat van bestemming is ongeldig"; +$lang['danger']['invalid_nexthop'] = "Formaat van nexthop is ongeldig"; +$lang['danger']['invalid_nexthop_authenticated'] = "Er bestaat al een nexthop met andere inloggegevens. Pas deze gegevens voor de reeds bestaande nexthop eerst aan."; +$lang['danger']['next_hop_interferes'] = "%s interfereert met nexthop %s"; +$lang['danger']['next_hop_interferes_any'] = "Een bestaande nexthop interfereert met %s"; $lang['danger']['rspamd_ui_pw_length'] = "Rspamd-wachtwoord moet minstens 6 tekens lang zijn"; $lang['success']['rspamd_ui_pw_set'] = "Rspamd-wachtwoord succesvol ingesteld"; +$lang['success']['queue_command_success'] = "Opdracht succesvol voltooid"; $lang['danger']['unknown'] = "Er is een onbekende fout opgetreden"; $lang['danger']['malformed_username'] = "Ongeldige gebruikersnaam"; $lang['info']['awaiting_tfa_confirmation'] = "In afwachting van tweefactorauthenticatie..."; $lang['success']['logged_in_as'] = "Succesvol ingelogd als %s"; $lang['danger']['login_failed'] = "Aanmelding mislukt"; -$lang['danger']['set_acl_failed'] = "ALC kon niet worden ingesteld"; +$lang['danger']['set_acl_failed'] = "Toegangscontrole kon niet worden ingesteld"; $lang['danger']['no_user_defined'] = "Geen gebruiker gespecificeerd"; -$lang['danger']['script_empty'] = "Script kan niet leeg zijn"; +$lang['danger']['script_empty'] = "Script dient ingevuld te worden"; $lang['danger']['sieve_error'] = "Sieve-fout: %s"; $lang['danger']['value_missing'] = "Niet alle waarden zijn ingevuld"; $lang['danger']['filter_type'] = "Verkeerd filtertype"; -$lang['danger']['domain_cannot_match_hostname'] = "Domein kan niet gelijk zijn aan hostname"; +$lang['danger']['domain_cannot_match_hostname'] = "Het domein dient af te wijken van de hostname"; $lang['warning']['domain_added_sogo_failed'] = "Domein is toegevoegd, maar het hestarten van SOGo mislukte. Controleer de serverlogs."; -$lang['danger']['rl_timeframe'] = "Ratelimit time frame is incorrect"; +$lang['danger']['rl_timeframe'] = "Ratelimit-tijdsbestek is ongeldig"; +$lang['success']['rl_saved'] = "Ratelimit voor object %s is opgeslagen"; +$lang['success']['acl_saved'] = "Toegangscontrole voor object %s is opgeslagen"; $lang['success']['deleted_syncjobs'] = "Synchronisatietaken %s zijn verwijderd"; $lang['success']['deleted_syncjob'] = "Synchronisatietaak %s is verwijderd"; $lang['success']['delete_filters'] = "Filters %s zijn verwijderd"; $lang['success']['delete_filter'] = "Filter %s is verwijderd"; $lang['danger']['invalid_bcc_map_type'] = "Ongeldig BCC-kaart type"; -$lang['danger']['bcc_empty'] = "BCC-bestemming kan niet leeg zijn"; +$lang['danger']['bcc_empty'] = "BCC-bestemming dient ingevuld te worden"; $lang['danger']['bcc_must_be_email'] = "BCC-kaart %s is geen geldig e-mailadres"; $lang['danger']['bcc_exists'] = "BCC-kaart %s bestaat voor type %s"; $lang['success']['bcc_saved'] = "BCC-kaart is opgeslagen"; $lang['success']['bcc_edited'] = "BCC-kaart %s is gewijzigd"; $lang['success']['bcc_deleted'] = "BCC-kaarten %s zijn verwijderd"; $lang['danger']['private_key_error'] = "Privรฉsleutel-fout: %s"; -$lang['danger']['map_content_empty'] = "Inhoud kan niet leeg zijn"; +$lang['danger']['map_content_empty'] = "Inhoud dient ingevuld te zijn"; $lang['success']['settings_map_added'] = "Instellingen toegevoegd"; $lang['danger']['settings_map_invalid'] = "Instellingen ongeldig"; $lang['danger']['settings_map_removed'] = "Instellingen verwijderd: %s"; $lang['danger']['invalid_host'] = "Ongeldige host gespecificeerd: %s"; -$lang['danger']['relayhost_invalid'] = "Relayhost %s is ongeldig"; +$lang['danger']['relayhost_invalid'] = "Invoer %s is ongeldig"; $lang['success']['saved_settings'] = "Instellingen opgeslagen"; $lang['success']['db_init_complete'] = "Database-initialisatie voltooid"; @@ -68,41 +83,44 @@ $lang['warning']['session_token'] = "Token ongeldig: komt niet overeen"; $lang['danger']['dkim_domain_or_sel_invalid'] = "DKIM-domein %s ongeldig"; $lang['success']['dkim_removed'] = "DKIM-sleutel %s is verwijderd"; -$lang['success']['dkim_added'] = "DKIM-sleutel is opgeslagen"; +$lang['success']['dkim_added'] = "DKIM-sleutel %s is opgeslagen"; +$lang['success']['dkim_duplicated'] = "DKIM-sleutel voor domein %s is gekopieerd naar %s"; $lang['danger']['access_denied'] = "Toegang geweigerd of ongeldige gegevens"; -$lang['danger']['domain_invalid'] = "Domeinnaam %s is ongeldig"; -$lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Max. postvakquotum is groter dan domeinquotum"; +$lang['danger']['domain_invalid'] = "Domeinnaam is ongeldig"; +$lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Maximaal postvakquotum is groter dan domeinquotum"; $lang['danger']['object_is_not_numeric'] = "Waarde %s is niet numeriek"; $lang['success']['domain_added'] = "Domein %s is toegevoegd"; -$lang['success']['items_deleted'] = "Onderdeel %s is verwijderd"; +$lang['success']['items_deleted'] = "Onderdelen %s zijn verwijderd"; $lang['success']['item_deleted'] = "Onderdeel %s is verwijderd"; $lang['danger']['alias_empty'] = "Aliasadres moet ingevuld worden"; $lang['danger']['last_key'] = 'De laatste sleutel kan niet worden verwijderd'; $lang['danger']['goto_empty'] = "Doeladres moet ingevuld worden"; -$lang['danger']['policy_list_from_exists'] = "Er bestaat al een vermelding met dezelfde naam"; +$lang['danger']['policy_list_from_exists'] = "Er bestaat reeds een vermelding met dezelfde naam"; $lang['danger']['policy_list_from_invalid'] = "Invoer is ongeldig"; $lang['danger']['alias_invalid'] = "Aliasadres %s is ongeldig"; $lang['danger']['goto_invalid'] = "Doeladres %s is ongeldig"; $lang['danger']['alias_domain_invalid'] = "Aliasdomein %s is ongeldig"; $lang['danger']['target_domain_invalid'] = "Doeladres %s is ongeldig"; -$lang['danger']['object_exists'] = "Object %s bestaat al"; -$lang['danger']['domain_exists'] = "Domain %s bestaat al"; +$lang['danger']['object_exists'] = "Object %s bestaat reeds"; +$lang['danger']['domain_exists'] = "Domain %s bestaat reeds"; $lang['danger']['alias_goto_identical'] = "Het alias- en doeladres mogen niet identiek zijn"; -$lang['danger']['aliasd_targetd_identical'] = "Aliasdomein %s mag niet identiek zijn aan het doeldomein"; -$lang['danger']['maxquota_empty'] = 'Max. postvakquotum moet groter dan 0 zijn.'; +$lang['danger']['aliasd_targetd_identical'] = "Aliasdomein %s dient af te wijken van het doeldomein"; +$lang['danger']['maxquota_empty'] = 'Maximaal postvakquotum moet groter dan 0 zijn.'; $lang['success']['alias_added'] = "Aliasadres %s is toegevoegd"; $lang['success']['alias_modified'] = "Wijzigingen aan alias %s zijn opgeslagen"; $lang['success']['mailbox_modified'] = "Wijzigingen aan postvak %s zijn opgeslagen"; $lang['success']['resource_modified'] = "Wijzigingen aan postvak %s zijn opgeslagen"; $lang['success']['object_modified'] = "Wijzigingen aan object %s zijn opgeslagen"; -$lang['success']['f2b_modified'] = "Wijzigingen aan Fail2ban parameters zijn opgeslagen"; +$lang['success']['f2b_modified'] = "Wijzigingen aan Fail2ban zijn opgeslagen"; $lang['danger']['targetd_not_found'] = "Doeldomein %s niet gevonden"; $lang['success']['aliasd_added'] = "Aliasdomein %s is toegevoegd"; $lang['success']['aliasd_modified'] = "Wijzigingen aan aliasadres %s zijn opgeslagen"; $lang['success']['domain_modified'] = "Wijzigingen aan domein %s zijn opgeslagen"; $lang['success']['domain_admin_modified'] = "Wijzigingen aan domeinbeheerder %s zijn opgeslagen"; $lang['success']['domain_admin_added'] = "Domeinbeheerder %s is toegevoegd"; -$lang['success']['admin_modified'] = "Wijzigingen aan de beheerder zijn opgeslagen"; +$lang['success']['admin_added'] = "Administrator %s is toegevoegd"; +$lang['success']['admin_modified'] = "Wijzigingen aan administrator zijn opgeslagen"; +$lang['success']['admin_api_modified'] = "Wijzigingen aan de API zijn opgeslagen"; $lang['danger']['username_invalid'] = "Gebruikersnaam %s kan niet worden gebruikt"; $lang['danger']['password_mismatch'] = "De ingevoerde wachtwoorden komen niet overeen"; $lang['danger']['password_complexity'] = "Wachtwoord voldoet niet aan de vereisten (6 tekens lang, letters en nummers)"; @@ -110,30 +128,33 @@ $lang['danger']['password_empty'] = "Het wachtwoord moet ingevuld worden"; $lang['danger']['login_failed'] = "Aanmelding mislukt"; $lang['danger']['mailbox_invalid'] = "Naam van het postvak is ongeldig"; $lang['danger']['description_invalid'] = 'Beschrijving voor %s is ongeldig'; -$lang['danger']['resource_invalid'] = "Naam van de hulpbron %s is ongeldig"; -$lang['danger']['is_alias'] = "Aliasadres %s bestaat al"; -$lang['danger']['is_alias_or_mailbox'] = "Aliasadres of postvak %s bestaat al"; -$lang['danger']['is_spam_alias'] = "Spam-aliasadres %s bestaat al"; +$lang['danger']['resource_invalid'] = "Naam van het middel %s is ongeldig"; +$lang['danger']['is_alias'] = "Aliasadres %s bestaat reeds"; +$lang['danger']['is_alias_or_mailbox'] = "Aliasadres of postvak %s bestaat reeds"; +$lang['danger']['is_spam_alias'] = "Spam-aliasadres %s bestaat reeds"; $lang['danger']['quota_not_0_not_numeric'] = "Quotum moet numeriek en groter dan 0 zijn"; $lang['danger']['domain_not_found'] = 'Domein %s niet gevonden'; -$lang['danger']['max_mailbox_exceeded'] = "Max. aantal postvakken overschreden (%d van %d)"; -$lang['danger']['max_alias_exceeded'] = 'Max. aantal aliassen overschreden'; +$lang['danger']['max_mailbox_exceeded'] = "Maximaal aantal postvakken overschreden (%d van %d)"; +$lang['danger']['max_alias_exceeded'] = 'Maximaal aantal aliassen overschreden'; $lang['danger']['mailbox_quota_exceeded'] = "Postvakquotum heeft het domeinlimiet overschreden (max. %d MiB)"; $lang['danger']['mailbox_quota_left_exceeded'] = "Onvoldoende ruimte beschikbaar (%d MiB)"; $lang['success']['mailbox_added'] = "Postvak %s is toegevoegd"; -$lang['success']['resource_added'] = "Hulpbron %s is toegevoegd"; +$lang['success']['resource_added'] = "Middel %s is toegevoegd"; $lang['success']['domain_removed'] = "Domein %s is verwijderd"; $lang['success']['alias_removed'] = "Alias %s is verwijderd"; $lang['success']['alias_domain_removed'] = "Aliasdomein %s is verwijderd"; $lang['success']['domain_admin_removed'] = "Domeinbeheerder %s is verwijderd"; +$lang['success']['admin_removed'] = "Administrator %s is verwijderd"; $lang['success']['mailbox_removed'] = "Postvak %s is verwijderd"; $lang['success']['eas_reset'] = "De ActiveSync-apparaatcache voor gebruiker %s is hersteld"; -$lang['success']['resource_removed'] = "Hulpbron %s is verwijderd"; +$lang['success']['resource_removed'] = "Middel %s is verwijderd"; +$lang['warning']['cannot_delete_self'] = "Gebruikers kunnen niet worden verwijderd wanneer deze zijn ingelogd"; +$lang['warning']['no_active_admin'] = "Het is niet mogelijk om de laatste actieve administrator te verwijderen"; $lang['danger']['max_quota_in_use'] = "Postvakquotum moet gelijk zijn aan, of groter zijn dan %d MiB"; $lang['danger']['domain_quota_m_in_use'] = "Domeinquotum moet gelijk zijn aan, of groter zijn dan %s MiB"; -$lang['danger']['mailboxes_in_use'] = "Max. aantal postvakken moet gelijk zijn aan, of groter zijn dan %d"; -$lang['danger']['aliases_in_use'] = "Max. aantal aliassen moet gelijk zijn aan, of groter zijn dan %d"; -$lang['danger']['sender_acl_invalid'] = "ACL-waarde van afzender %s is ongeldig"; +$lang['danger']['mailboxes_in_use'] = "Maximaal aantal postvakken moet gelijk zijn aan, of groter zijn dan %d"; +$lang['danger']['aliases_in_use'] = "Maximaal aantal aliassen moet gelijk zijn aan, of groter zijn dan %d"; +$lang['danger']['sender_acl_invalid'] = "Toegangscontrole van afzender %s is ongeldig"; $lang['danger']['domain_not_empty'] = "Kan geen domein in gebruik verwijderen"; $lang['danger']['validity_missing'] = 'Wijs een geldigheidstermijn toe'; $lang['user']['loading'] = "Bezig met laden..."; @@ -150,18 +171,18 @@ $lang['user']['change_password'] = 'Verander wachtwoord'; $lang['user']['client_configuration'] = "Laat configuratiegidsen voor e-mailprogramma's zien"; $lang['user']['new_password'] = 'Nieuw wachtwoord'; $lang['user']['save_changes'] = 'Wijzigingen opslaan'; -$lang['user']['password_now'] = 'Huidig wachtwoord (ter bevestiging)'; +$lang['user']['password_now'] = 'Huidig wachtwoord'; $lang['user']['new_password_repeat'] = 'Herhaal wachtwoord'; $lang['user']['new_password_description'] = 'Vereisten: 6 tekens lang, letters en nummers'; $lang['user']['spam_aliases'] = 'Tijdelijk e-mailadres'; $lang['user']['alias'] = 'Alias'; $lang['user']['shared_aliases'] = 'Gedeelde aliasadressen'; -$lang['user']['shared_aliases_desc'] = 'Een gedeeld aliasadres wordt niet beรฏnvloed door gebruiker-specifieke instellingen. Een aangepast spamfilter kan worden ingesteld door een beheerder, doormiddel van domeinbeleid.'; +$lang['user']['shared_aliases_desc'] = 'Een gedeeld aliasadres wordt niet beรฏnvloed door gebruiker-specifieke instellingen. Een aangepast spamfilter kan eventueel worden ingesteld door een beheerder.'; $lang['user']['direct_aliases'] = 'Directe aliasadressen'; $lang['user']['direct_aliases_desc'] = 'Directe aliasadressen worden beรฏnvloed door spamfilters en het versleutelingsbeleid.'; -$lang['user']['is_catch_all'] = 'Catch-all voor domein(en)'; -$lang['user']['aliases_also_send_as'] = 'Ook toegestaan om te verzenden als gebruiker'; -$lang['user']['aliases_send_as_all'] = 'Controleer verzendtoegang voor de volgende domein(en), inclusief zijn aliassen, niet'; +$lang['user']['is_catch_all'] = 'Catch-all voor domeinen'; +$lang['user']['aliases_also_send_as'] = 'Toegestaan om te verzenden als'; +$lang['user']['aliases_send_as_all'] = 'Controleer verzendtoegang voor de volgende domeinen, inclusief aliassen, niet'; $lang['user']['alias_create_random'] = 'Creรซer willekeurig alias'; $lang['user']['alias_extend_all'] = 'Verleng alias met 1 uur'; $lang['user']['alias_valid_until'] = 'Geldig tot'; @@ -170,26 +191,27 @@ $lang['user']['alias_time_left'] = 'Resterende tijd'; $lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; $lang['user']['alias_select_validity'] = 'Geldigheid'; $lang['user']['sync_jobs'] = 'Synchronisatietaken'; -$lang['user']['hour'] = 'Uur'; -$lang['user']['hours'] = 'Uren'; -$lang['user']['day'] = 'Dag'; -$lang['user']['week'] = 'Week'; -$lang['user']['weeks'] = 'Weken'; +$lang['user']['expire_in'] = 'Verloopt over'; +$lang['user']['hour'] = 'uur'; +$lang['user']['hours'] = 'uren'; +$lang['user']['day'] = 'dag'; +$lang['user']['week'] = 'week'; +$lang['user']['weeks'] = 'weken'; $lang['user']['spamfilter'] = 'Spamfilter'; $lang['admin']['spamfilter'] = 'Spamfilter'; $lang['user']['spamfilter_wl'] = 'Witte lijst'; -$lang['user']['spamfilter_wl_desc'] = 'Zet e-mailadressen op de witte lijst om ze <b>nooit</b> als spam te classificeren. Wildcards (*) zijn toegestaan. Deze filters worden niet toegepast op een gedeeld aliasadres.'; +$lang['user']['spamfilter_wl_desc'] = 'Zet e-mailadressen op de witte lijst om ze <b>nooit</b> als spam te markeren.<br>Deze lijst wordt niet toegepast op een gedeeld aliasadres.<br>Wildcards (*) zijn toegestaan.'; $lang['user']['spamfilter_bl'] = 'Zwarte lijst'; -$lang['user']['spamfilter_bl_desc'] = 'Zet e-mailadressen op de zwarte lijst om ze <b>altijd</b> als spam te classificeren, en op die manier te weigeren. Wildcards (*) zijn toegestaan. Deze filters worden niet toegepast op een gedeeld aliasadres.'; +$lang['user']['spamfilter_bl_desc'] = 'Zet e-mailadressen op de zwarte lijst om ze <b>altijd</b> als spam te markeren.<br>Deze lijst wordt niet toegepast op een gedeeld aliasadres.<br>Wildcards (*) zijn toegestaan.'; $lang['user']['spamfilter_behavior'] = 'Beoordeling'; $lang['user']['spamfilter_table_rule'] = 'Regel'; $lang['user']['spamfilter_table_action'] = 'Handeling'; $lang['user']['spamfilter_table_empty'] = 'Geen gegevens om weer te geven'; $lang['user']['spamfilter_table_remove'] = 'verwijder'; $lang['user']['spamfilter_table_add'] = 'Voeg toe'; -$lang['user']['spamfilter_green'] = 'Groen: dit bericht is geen spam'; -$lang['user']['spamfilter_yellow'] = 'Geel: dit bericht is mogelijk spam en zal worden geplaatst in de ongewenste mail'; -$lang['user']['spamfilter_red'] = 'Rood: dit bericht is spam en zal, op basis van de instellingen, worden geweigerd of in de quarantaine worden geplaatst'; +$lang['user']['spamfilter_green'] = 'Groen: dit bericht is geen spam.'; +$lang['user']['spamfilter_yellow'] = 'Geel: dit bericht is mogelijk spam en zal in de spamfolder geplaatst worden.'; +$lang['user']['spamfilter_red'] = 'Rood: dit bericht is spam en zal, op basis van de instellingen, worden geweigerd of in de quarantaine worden geplaatst.'; $lang['user']['spamfilter_default_score'] = 'Standaardwaarden:'; $lang['user']['spamfilter_hint'] = 'De eerste waarde omschrijft een lage spamscore, de tweede een hoge spamscore.'; $lang['user']['spamfilter_table_domain_policy'] = "n.v.t. (domeinbeleid)"; @@ -197,22 +219,26 @@ $lang['user']['waiting'] = "Wachten"; $lang['user']['status'] = "Status"; $lang['user']['running'] = "Wordt uitgevoerd"; -$lang['user']['tls_policy_warning'] = '<strong>Let wel:</strong> Door versleutelde e-mails te forceren, worden mogelijk niet alle e-mails afgeleverd.<br>Berichten die niet aan het ingestelde beleid voldoen, worden resoluut geweigerd.<br>Dit is van toepassing op het primaire e-mailadres, inclusief alle aliassen met <b>enkel dit postvak</b> als doel.'; +$lang['user']['tls_policy_warning'] = '<strong>Let wel:</strong> Door versleuteling te forceren, worden mogelijk niet alle e-mails afgeleverd.<br>Berichten die niet aan het ingestelde beleid voldoen, worden resoluut geweigerd.<br>Dit is van toepassing op het primaire e-mailadres, inclusief alle <b>directe</b> aliasadressen.'; $lang['user']['tls_policy'] = 'Versleutelingsbeleid'; -$lang['user']['tls_enforce_in'] = 'Forceer inkomend'; -$lang['user']['tls_enforce_out'] = 'Forceer uitgaand'; +$lang['user']['tls_enforce_in'] = 'Vereis inkomend'; +$lang['user']['tls_enforce_out'] = 'Vereis uitgaand'; $lang['user']['no_record'] = 'Geen vermelding'; - -$lang['user']['tag_handling'] = 'Omgaan met e-mailtags'; +$lang['user']['tag_handling'] = 'E-mailtags'; $lang['user']['tag_in_subfolder'] = 'In submap'; $lang['user']['tag_in_subject'] = 'In onderwerp'; $lang['user']['tag_in_none'] = 'Niets doen'; -$lang['user']['tag_help_explain'] = 'In submap: er wordt een nieuwe map aangemaakt, genoemd naar de tag (bijv.: "INBOX/Apple").<br>In onderwerp: de tag wordt vรณรณr het oorspronkelijke onderwerp geplaatst (bijv.: "[Apple] Uw bestelling").'; -$lang['user']['tag_help_example'] = 'Voorbeeld van een e-maildres met tag: ik<b>+Apple</b>@example.org'; +$lang['user']['tag_help_explain'] = 'In submap: er wordt een nieuwe map aangemaakt, genoemd naar de tag (bijv.: "INBOX/Tesla").<br>In onderwerp: de tag wordt vรณรณr het oorspronkelijke onderwerp geplaatst (bijv.: "[Tesla] Uw serviceafspraak").'; +$lang['user']['tag_help_example'] = 'Voorbeeld van een e-maildres met tag: ik<b>+Tesla</b>@example.org'; + $lang['user']['eas_reset'] = 'Herstel ActiveSync-apparaatcache'; $lang['user']['eas_reset_now'] = 'Herstel nu'; -$lang['user']['eas_reset_help'] = 'In de meeste gevallen helpt dit tegen problemen met ActiveSync op uw apparaten<br><b>Let wel:</b> Alle onderdelen worden opnieuw gedownload'; +$lang['user']['eas_reset_help'] = 'In de meeste gevallen verhelpt dit problemen met ActiveSync op je apparaten<br><b>Let wel:</b> alle onderdelen zullen opnieuw gedownload moeten worden!'; + +$lang['user']['sogo_profile_reset'] = 'Herstel SOGo-profiel'; +$lang['user']['sogo_profile_reset_now'] = 'Herstel nu'; +$lang['user']['sogo_profile_reset_help'] = 'Dit zal het SOGo-profiel van deze gebruiker verwijderen, en daarbij <b>alle gegevens permanent verwijderen</b>.'; $lang['user']['encryption'] = 'Versleuteling'; $lang['user']['username'] = 'Gebruikersnaam'; @@ -225,14 +251,27 @@ $lang['user']['edit'] = 'Wijzig'; $lang['user']['remove'] = 'Verwijder'; $lang['user']['create_syncjob'] = 'Voeg een nieuwe synchronisatietaak toe'; -$lang['start']['mailcow_apps_detail'] = 'Gebruik een Mailcow-app om uw e-mails, agenda, contacten en meer te bekijken.'; +$lang['start']['mailcow_apps_detail'] = 'Gebruik een Mailcow-app om je e-mails, agenda, contacten en meer te bekijken.'; $lang['start']['mailcow_panel_detail'] = '<b>Domeinbeheerders</b> kunnen postvakken en aliassen aanmaken, wijzigen en verwijderen. Ook kunnen ze domeinen aanpassen en informatie over deze verkrijgen.<br><b>Gebruikers</b> kunnen tijdelijke aliassen aanmaken, hun wachtwoord aanpassen en de spamfilterinstellingen wijzigen.'; -$lang['start']['imap_smtp_server_auth_info'] = 'Gebruik uw volledige e-mailadres en het onversleutelde verificatiemechanisme.<br>De aanmeldgegevens worden versleuteld verstuurd.'; +$lang['start']['imap_smtp_server_auth_info'] = 'Gebruik je volledige e-mailadres en het bijbehorende (onversleutelde) verificatiemechanisme.<br>De aanmeldgegevens worden versleuteld verstuurd.'; $lang['start']['help'] = 'Toon/verberg hulppaneel'; $lang['header']['mailcow_settings'] = 'Instellingen'; -$lang['header']['administration'] = 'Administratief'; -$lang['header']['mailboxes'] = 'Postvakken'; +$lang['header']['administration'] = 'Configuratie & details'; +$lang['header']['mailboxes'] = 'Mailconfiguratie'; $lang['header']['user_settings'] = 'Gebruikersinstellingen'; +$lang['header']['quarantine'] = "Quarantaine"; +$lang['header']['debug'] = "Systeeminformatie"; +$lang['quarantine']['disabled_by_config'] = "De huidige systeemconfiguratie deactiveert de quarantainefunctionaliteit."; +$lang['mailbox']['tls_policy_maps'] = 'Globaal versleutelingsbeleid'; +$lang['mailbox']['tls_policy_maps_long'] = 'Uitgaand versleutelingsbeleid'; +$lang['mailbox']['tls_policy_maps_info'] = 'Deze opties worden boven het versleutelingsbeleid van een gebruiker verkozen.<br>Bekijk <a href="http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps" target="_blank">de documentatie</a> voor meer informatie.'; +$lang['mailbox']['tls_enforce_in'] = 'Forceer inkomende versleuteling'; +$lang['mailbox']['tls_enforce_out'] = 'Forceer uitgaande versleuteling'; +$lang['mailbox']['tls_map_dest'] = 'Bestemming'; +$lang['mailbox']['tls_map_dest_info'] = 'Voorbeeld: example.org, .example.org, mail@example.org, [mail.example.org]:25'; +$lang['mailbox']['tls_map_policy'] = 'Beleid'; +$lang['mailbox']['tls_map_parameters'] = 'Parameters'; +$lang['mailbox']['tls_map_parameters_info'] = 'Voorbeeld: protocols=!SSLv2 ciphers=medium exclude=3DES'; $lang['mailbox']['booking_0'] = 'Laat altijd zien als vrij'; $lang['mailbox']['booking_lt0'] = 'Onbeperkt, maar laat zien als bezet wanneer geboekt'; $lang['mailbox']['booking_custom'] = 'Zet vast op een specifiek aantal boekingen'; @@ -247,9 +286,12 @@ $lang['mailbox']['description'] = 'Beschrijving'; $lang['mailbox']['alias'] = 'Alias'; $lang['mailbox']['aliases'] = 'Aliassen'; $lang['mailbox']['domains'] = 'Domeinen'; +$lang['admin']['domain'] = 'Domein'; +$lang['admin']['domain_s'] = 'Domein(en)'; $lang['mailbox']['mailboxes'] = 'Postvakken'; -$lang['mailbox']['resources'] = 'Hulpbronnen'; -$lang['mailbox']['mailbox_quota'] = 'Max. grootte van een postvak'; +$lang['mailbox']['mailbox'] = 'Postvak'; +$lang['mailbox']['resources'] = 'Middelen'; +$lang['mailbox']['mailbox_quota'] = 'Maximale postvakgrootte'; $lang['mailbox']['domain_quota'] = 'Quotum'; $lang['mailbox']['active'] = 'Actief'; $lang['mailbox']['action'] = 'Handeling'; @@ -260,8 +302,6 @@ $lang['mailbox']['target_address'] = 'Doeladres'; $lang['mailbox']['username'] = 'Gebruikersnaam'; $lang['mailbox']['fname'] = 'Volledige naam'; $lang['mailbox']['filter_table'] = 'Filtertabel'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'In gebruik (%)'; $lang['mailbox']['msg_num'] = 'Bericht #'; $lang['mailbox']['remove'] = 'Verwijder'; @@ -271,7 +311,7 @@ $lang['mailbox']['no_record_single'] = 'Geen vermelding'; $lang['mailbox']['add_domain'] = 'Voeg domein toe'; $lang['mailbox']['add_domain_alias'] = 'Voeg domeinalias toe'; $lang['mailbox']['add_mailbox'] = 'Voeg postvak toe'; -$lang['mailbox']['add_resource'] = 'Voeg hulpbron toe'; +$lang['mailbox']['add_resource'] = 'Voeg middel toe'; $lang['mailbox']['add_alias'] = 'Voeg alias toe'; $lang['mailbox']['add_domain_record_first'] = 'Voeg eerst een domein toe'; $lang['mailbox']['empty'] = 'Geen resultaten'; @@ -284,7 +324,7 @@ $lang['mailbox']['mins_interval'] = 'Interval (min)'; $lang['mailbox']['last_run'] = 'Laatst uitgevoerd'; $lang['mailbox']['excludes'] = 'Exclusief'; $lang['mailbox']['last_run_reset'] = 'Plan volgende'; -$lang['mailbox']['sieve_info'] = 'Het is mogelijk om meerdere filters per gebruiker in te stellen, maar er kan slechts รฉรฉn pre-filter en รฉรฉn post-filter tegelijkertijd actief zijn.<br>Elk filter zal in de aangegeven volgorde worden verwerkt. Noch een mislukt script, noch een gespecificeerde "keep;" stopt met het verwerken van volgende scripts.<br>Pre-filter โ Gebruikersscripts โ Post-filter โ <a href="https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after" target="_blank">Globaal filter</a>'; +$lang['mailbox']['sieve_info'] = 'Het is mogelijk om meerdere filters per gebruiker in te stellen, maar er kan slechts รฉรฉn voorfilter en รฉรฉn nafilter tegelijkertijd actief zijn.<br>Elk filter zal in de aangegeven volgorde worden verwerkt. Noch een mislukt script, noch een gespecificeerde "keep;" stopt met het verwerken van volgende scripts.<br>Voorfilter โ Gebruikersscripts โ Nafilter โ <a href="https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after" target="_blank">Globaal filter</a>'; $lang['info']['no_action'] = 'Geen handeling van toepassing'; $lang['edit']['syncjob'] = 'Wijzig synchronisatietaak'; @@ -296,7 +336,7 @@ $lang['edit']['redirect_uri'] = 'Doorstuur/Callback URL'; $lang['edit']['hostname'] = 'Hostname'; $lang['edit']['encryption'] = 'Versleuteling'; $lang['edit']['maxage'] = 'Maximale leeftijd van berichten (in dagen) die extern worden opgehaald<br><small>(0 = negeer leeftijd)</small>'; -$lang['edit']['maxbytespersecond'] = 'Max. bytes per seconde <br><small>(0 = onbeperkt)</small>'; +$lang['edit']['maxbytespersecond'] = 'Maximale bytes per seconde <br><small>(0 = onbeperkt)</small>'; $lang['edit']['automap'] = 'Probeer mappen automatisch te koppelen ("Verstuurde items", "Verstuurd" => "Verstuurd" etc.)'; $lang['edit']['skipcrossduplicates'] = 'Sla duplicaten verspreid over mappen over (wie het eerst komt, het eerst maalt)'; $lang['add']['automap'] = 'Probeer mappen automatisch te koppelen ("Verstuurde items", "Verstuurd" => "Verstuurd" etc.)'; @@ -306,12 +346,16 @@ $lang['edit']['mins_interval'] = 'Interval (min)'; $lang['edit']['exclude'] = 'Sluit objecten uit (regex)'; $lang['edit']['save'] = 'Wijzigingen opslaan'; $lang['edit']['username'] = 'Gebruikersnaam'; -$lang['edit']['max_mailboxes'] = 'Max. mogelijke postvakken'; +$lang['edit']['max_mailboxes'] = 'Maximaal aantal postvakken'; $lang['edit']['title'] = 'Wijzig object'; $lang['edit']['target_address'] = 'Doeladres(sen) <small>(kommagescheiden)</small>'; $lang['edit']['active'] = 'Actief'; +$lang['edit']['gal'] = 'Globale adreslijst'; +$lang['edit']['gal_info'] = 'De globale adreslijst bevat alle objecten van een domein. Deze kunnen door geen enkele gebruiker worden bewerkt. <b>Herstart SOGo om wijzigingen door te voeren.</b>'; $lang['edit']['force_pw_update'] = 'Vereis nieuw wachtwoord bij eerstvolgende login'; -$lang['edit']['force_pw_update_info'] = 'Deze gebruiker kan, wanneer ingeschakeld, enkel inloggen op Mailcow UI totdat de procedure is doorlopen.'; +$lang['edit']['force_pw_update_info'] = 'Deze gebruiker kan hierdoor enkel inloggen op Mailcow UI, totdat de procedure succesvol doorlopen is.'; +$lang['edit']['sogo_access'] = 'Geef toegang to SOGo'; +$lang['edit']['sogo_access_info'] = 'Deze gebruiker kan, wanneer ingeschakeld, gebruikmaken van SOGo. Deze instelling heeft niks te maken met de rest van de services. Ook zal het SOGo-profiel van deze gebruiker bewaard blijven.'; $lang['edit']['target_domain'] = 'Doeldomein'; $lang['edit']['password'] = 'Wachtwoord'; $lang['edit']['password_repeat'] = 'Herhaal wachtwoord'; @@ -322,26 +366,71 @@ $lang['edit']['domains'] = 'Domeinen'; $lang['edit']['alias'] = 'Wijzig alias'; $lang['edit']['mailbox'] = 'Wijzig postvak'; $lang['edit']['description'] = 'Beschrijving'; -$lang['edit']['max_aliases'] = 'Max. aantal aliassen'; -$lang['edit']['max_quota'] = 'Max. postvakquotum (MiB)'; -$lang['edit']['domain_quota'] = 'Domeinquotum'; +$lang['edit']['max_aliases'] = 'Maximaal aantal aliassen'; +$lang['edit']['max_quota'] = 'Postvakquotum (MiB)'; +$lang['edit']['domain_quota'] = 'Domeinquotum (MiB)'; $lang['edit']['backup_mx_options'] = 'Secundaire MX'; $lang['edit']['relay_domain'] = 'Schakel dit domein door'; $lang['edit']['relay_all'] = 'Schakel alle ontvangers door'; -$lang['edit']['relay_all_info'] = '<small>Wanneer er wordt gekozen om <b>niet</b> alle ontvangers door te schakelen, dient er per ontvanger een blind postvak aangemaakt te worden.</small>'; +$lang['edit']['relay_all_info'] = '<small>Wanneer er wordt gekozen om <b>niet</b> alle ontvangers door te schakelen, dient er per ontvanger een leeg postvak aangemaakt te worden.</small>'; $lang['edit']['full_name'] = 'Volledige naam'; $lang['edit']['quota_mb'] = 'Quotum (MiB)'; $lang['edit']['sender_acl'] = 'Sta toe om te verzenden als'; $lang['edit']['sender_acl_disabled'] = 'โณ <span class="label label-danger">Verzendcontrole is uitgeschakeld</span>'; +$lang['user']['sender_acl_disabled'] = 'โณ <span class="label label-danger">Verzendcontrole is uitgeschakeld</span>'; $lang['edit']['previous'] = 'Vorige pagina'; $lang['edit']['unchanged_if_empty'] = 'Laat leeg wanneer onveranderd'; $lang['edit']['dont_check_sender_acl'] = "Schakel verzendcontrole uit voor domein %s (inclusief aliasdomeinen)"; $lang['edit']['multiple_bookings'] = 'Meerdere boekingen'; $lang['edit']['kind'] = 'Soort'; -$lang['edit']['resource'] = 'Hulpbron'; +$lang['edit']['resource'] = 'Middel'; +$lang['edit']['relayhost'] = 'Afzender-afhankelijke transportkaarten'; +$lang['edit']['public_comment'] = 'Publieke opmerking'; +$lang['mailbox']['public_comment'] = 'Publieke opmerking'; +$lang['edit']['private_comment'] = 'Persoonlijke opmerking'; +$lang['mailbox']['private_comment'] = 'Persoonlijke opmerking'; +$lang['edit']['comment_info'] = 'Een persoonlijke opmerking is niet zichtbaar voor de gebruiker, terwijl een publieke opmerking wel weergegeven zal worden in het overzicht van deze gebruiker.'; +$lang['add']['public_comment'] = 'Publieke opmerking'; +$lang['add']['private_comment'] = 'Persoonlijke opmerking'; +$lang['add']['comment_info'] = 'Een persoonlijke opmerking is niet zichtbaar voor de gebruiker, terwijl een publieke opmerking wel weergegeven zal worden in het overzicht van deze gebruiker.'; +$lang['acl']['spam_alias'] = 'Tijdelijke aliassen'; +$lang['acl']['tls_policy'] = 'Versleutelingsbeleid'; +$lang['acl']['spam_score'] = 'Spamscore'; +$lang['acl']['spam_policy'] = 'Blacklist/Whitelist'; +$lang['acl']['delimiter_action'] = 'Delimiter-actie'; +$lang['acl']['syncjobs'] = 'Synchronisatietaken'; +$lang['acl']['eas_reset'] = 'Herstel ActiveSync-apparaatcache'; +$lang['acl']['sogo_profile_reset'] = 'Herstel SOGo-profiel'; +$lang['acl']['quarantine'] = 'Quarantaine-acties'; +$lang['acl']['quarantine_notification'] = 'Quarantaine-notificaties'; +$lang['acl']['quarantine_attachments'] = 'Quarantaine-bijlagen'; +$lang['acl']['alias_domains'] = 'Voeg aliasdomeinen toe'; +$lang['acl']['login_as'] = 'Log in als postvakgebruiker'; +$lang['acl']['bcc_maps'] = 'BCC-kaarten'; +$lang['acl']['filters'] = 'Filters'; +$lang['acl']['ratelimit'] = 'Ratelimit'; +$lang['acl']['recipient_maps'] = 'Ontvanger-kaarten'; +$lang['acl']['prohibited'] = 'Toegang geweigerd'; +$lang['mailbox']['quarantine_notification'] = 'Quarantaine-notificaties'; +$lang['mailbox']['never'] = 'Nooit'; +$lang['mailbox']['hourly'] = 'Ieder uur'; +$lang['mailbox']['daily'] = 'Dagelijks'; +$lang['mailbox']['weekly'] = 'Wekelijks'; +$lang['user']['quarantine_notification'] = 'Quarantaine-notificaties'; +$lang['user']['never'] = 'Nooit'; +$lang['user']['hourly'] = 'Ieder uur'; +$lang['user']['daily'] = 'Dagelijks'; +$lang['user']['weekly'] = 'Wekelijks'; +$lang['user']['quarantine_notification_info'] = 'Zodra een notificatie is verzonden, worden de items als gelezen gemarkeerd en zullen er geen meldingen meer over diezelfde items verstuurd worden.'; + +$lang['add']['generate'] = 'genereer'; $lang['add']['syncjob'] = 'Voeg een nieuwe synchronisatietaak toe'; -$lang['add']['syncjob_hint'] = 'Wees ervan bewust dat wachtwoorden onversleuteld moeten worden opgeslagen.'; +$lang['add']['syncjob_hint'] = 'Wees ervan bewust dat de authenticatiedata onversleuteld wordt opgeslagen!'; +$lang['add']['hostname'] = 'Host'; +$lang['add']['destination'] = 'Bestemming'; +$lang['add']['nexthop'] = 'Nexthop'; +$lang['edit']['nexthop'] = 'Nexthop'; $lang['add']['hostname'] = 'Hostname'; $lang['add']['port'] = 'Poort'; $lang['add']['username'] = 'Gebruikersnaam'; @@ -356,17 +445,19 @@ $lang['edit']['delete1'] = 'Verwijder van oorsprong wanneer voltooid'; $lang['edit']['delete2'] = 'Verwijder berichten die zich niet in de oorsprong bevinden'; $lang['add']['custom_params'] = 'Aangepaste parameters'; $lang['add']['subscribeall'] = 'Abonneer op alle mappen'; -$lang['add']['timeout1'] = 'Time-out voor verbinding met externe host'; -$lang['add']['timeout2'] = 'Time-out voor verbinding met lokale host'; +$lang['add']['timeout1'] = 'Time-out voor verbinding met externe hosts'; +$lang['add']['timeout2'] = 'Time-out voor verbinding met lokale hosts'; +$lang['edit']['timeout1'] = 'Time-out voor verbinding met externe hosts'; +$lang['edit']['timeout2'] = 'Time-out voor verbinding met lokale hosts'; $lang['add']['domain_matches_hostname'] = 'Domein %s komt overeen met hostname'; $lang['add']['domain'] = 'Domein'; $lang['add']['active'] = 'Actief'; $lang['add']['multiple_bookings'] = 'Meerdere boekingen'; $lang['add']['description'] = 'Beschrijving'; -$lang['add']['max_aliases'] = 'Max. mogelijke aliassen'; -$lang['add']['max_mailboxes'] = 'Max. mogelijke postvakken'; -$lang['add']['mailbox_quota_m'] = 'Max. postvakquotum (MiB)'; +$lang['add']['max_aliases'] = 'Maximaal aantal aliassen'; +$lang['add']['max_mailboxes'] = 'Maximaal aantal postvakken'; +$lang['add']['mailbox_quota_m'] = 'Maximaal postvakquotum (MiB)'; $lang['add']['domain_quota_m'] = 'Totale domeinquotum (MiB)'; $lang['add']['backup_mx_options'] = 'Secundaire MX'; $lang['add']['relay_all'] = 'Schakel alle ontvangers door'; @@ -399,8 +490,8 @@ $lang['add']['sieve_desc'] = 'Korte beschrijving'; $lang['edit']['sieve_desc'] = 'Korte beschrijving'; $lang['add']['sieve_type'] = 'Filtertype'; $lang['edit']['sieve_type'] = 'Filtertype'; -$lang['mailbox']['set_prefilter'] = 'Stel in als pre-filter'; -$lang['mailbox']['set_postfilter'] = 'Stel in als post-filter'; +$lang['mailbox']['set_prefilter'] = 'Stel in als voorfilter'; +$lang['mailbox']['set_postfilter'] = 'Stel in als nafilter'; $lang['mailbox']['filters'] = 'Filters'; $lang['mailbox']['sync_jobs'] = 'Synchronisatietaken'; $lang['mailbox']['inactive'] = 'Inactief'; @@ -420,34 +511,41 @@ $lang['tfa']['key_id_totp'] = "Geef deze key een naam"; $lang['tfa']['api_register'] = 'Mailcow maakt gebruik van de Yubico Cloud API. Om dit te benutten heeft u een API-sleutel van Yubico nodig, deze kunt u <a href="https://upgrade.yubico.com/getapikey/" target="_blank">hier</a> opvragen'; $lang['tfa']['u2f'] = "U2F"; $lang['tfa']['none'] = "Deactiveer"; -$lang['tfa']['delete_tfa'] = "Zet TFA uit"; -$lang['tfa']['disable_tfa'] = "Zet TFA uit tot de eerstvolgende succesvolle login"; +$lang['tfa']['delete_tfa'] = "Schakel tweefactorauthenticatie uit"; +$lang['tfa']['disable_tfa'] = "Pauzeer tweefactorauthenticatie tot de eerstvolgende succesvolle login"; $lang['tfa']['confirm'] = "Bevestig"; -$lang['tfa']['totp'] = "TOTP (Google Authenticator etc.)"; +$lang['tfa']['totp'] = "TOTP (30-secondecode)"; $lang['tfa']['select'] = "Selecteer..."; -$lang['tfa']['waiting_usb_auth'] = "<i>In afwachting van USB-apparaat...</i><br><br>Druk nu op de knop van uw U2F-apparaat."; -$lang['tfa']['waiting_usb_register'] = "<i>In afwachting van USB-apparaat...</i><br><br>Voer uw wachtwoord hierboven in en bevestig de registratie van het U2F-apparaat door op de knop van het apparaat te drukken."; -$lang['tfa']['scan_qr_code'] = "Scan de volgende QR-code met uw authenticatie-app:"; -$lang['tfa']['enter_qr_code'] = "Voer deze code in als uw apparaat geen QR-codes kan scannen:"; -$lang['tfa']['confirm_totp_token'] = "Bevestig de wijzigingen door de, door uw authenticatie-app gegenereerde code, in te voeren."; +$lang['tfa']['waiting_usb_auth'] = "<i>In afwachting van USB-apparaat...</i><br><br>Druk nu op de knop van je U2F-apparaat."; +$lang['tfa']['waiting_usb_register'] = "<i>In afwachting van USB-apparaat...</i><br><br>Voer je wachtwoord hierboven in en bevestig de registratie van het U2F-apparaat door op de knop van het apparaat te drukken."; +$lang['tfa']['scan_qr_code'] = "Scan de volgende QR-code met je authenticatie-app:"; +$lang['tfa']['enter_qr_code'] = "Voer deze code in als je apparaat geen QR-codes kan scannen:"; +$lang['tfa']['confirm_totp_token'] = "Bevestig de wijzigingen door de, door je authenticatie-app gegenereerde code, in te voeren."; $lang['admin']['rspamd-com_settings'] = '<a href="https://rspamd.com/doc/configuration/settings.html#settings-structure" target="_blank">Rspamd documentatie</a> - Een beschrijving voor deze instelling zal automatisch worden gegenereerd, bekijk de onderstaande presets voor meer info.'; $lang['admin']['no_new_rows'] = 'Er zijn geen extra rijen beschikbaar'; +$lang['admin']['queue_manager'] = 'Wachtrijbeheerder'; $lang['admin']['additional_rows'] = ' extra rijen zijn toegevoegd'; // parses to 'n additional rows were added' $lang['admin']['private_key'] = 'Privรฉsleutel'; $lang['admin']['import'] = 'Importeer'; +$lang['admin']['duplicate'] = 'Dupliceer'; $lang['admin']['import_private_key'] = 'Importeer privรฉsleutel'; -$lang['admin']['f2b_parameters'] = 'Fail2ban parameters'; +$lang['admin']['duplicate_dkim'] = 'Dupliceer DKIM-sleutel'; +$lang['admin']['dkim_from'] = 'Van'; +$lang['admin']['dkim_to'] = 'Naar'; +$lang['admin']['dkim_from_title'] = 'Kopieer data van domein'; +$lang['admin']['dkim_to_title'] = 'Doeldomein(en) - worden overgeschreven'; +$lang['admin']['f2b_parameters'] = 'Fail2ban-instellingen'; $lang['admin']['f2b_ban_time'] = 'Verbanningstijd (s)'; -$lang['admin']['f2b_max_attempts'] = 'Max. pogingen'; -$lang['admin']['f2b_retry_window'] = 'Tijdsbestek voor max. pogingen (s)'; +$lang['admin']['f2b_max_attempts'] = 'Maximaal aantal pogingen'; +$lang['admin']['f2b_retry_window'] = 'Tijdsbestek voor maximale pogingen (s)'; $lang['admin']['f2b_netban_ipv4'] = 'Voer de IPv4-subnetgrootte in waar de verbanning van kracht moet zijn (8-32)'; $lang['admin']['f2b_netban_ipv6'] = 'Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)'; $lang['admin']['f2b_whitelist'] = 'Netwerken/hosts op de witte lijst'; $lang['admin']['f2b_blacklist'] = 'Netwerken/hosts op de zwarte lijst'; -$lang['admin']['f2b_list_info'] = 'Een host of netwerk op de zwarte lijst heeft altijd hogere prioriteit dan eenzelfde op de witte lijst. Vermeldingen op de zwarte lijst worden aangemaakt bij het opstarten van de container, terwijl vermeldingen op de witte lijst elke keer vรณรณrdat een verbanning wordt toegepast worden gecheckt.'; -$lang['admin']['search_domain_da'] = 'Zoekdomeinen'; +$lang['admin']['f2b_list_info'] = 'Wees ervan bewust dat een host of netwerk op de zwarte lijst altijd hogere prioriteit heeft dan eenzelfde op de witte lijst.'; +$lang['admin']['search_domain_da'] = 'Zoek domeinen'; $lang['admin']['r_inactive'] = 'Inactieve beperkingen'; $lang['admin']['r_active'] = 'Actieve beperkingen'; $lang['admin']['r_info'] = 'Grijze elementen op de lijst van actieve beperkingen zijn niet geldig en kunnen niet worden verplaatst. Onbekende beperkingen zullen hoe dan ook in volgorde van weergave worden ingesteld. <br>Er kunnen nieuwe elementen worden toegevoegd in <code>inc/vars.local.inc.php</code> om ze te kunnen gebruiken.'; @@ -455,8 +553,11 @@ $lang['admin']['dkim_key_length'] = 'DKIM-sleutelgrootte (bits)'; $lang['admin']['dkim_key_valid'] = 'Sleutel geldig'; $lang['admin']['dkim_key_unused'] = 'Sleutel ongebruikt'; $lang['admin']['dkim_key_missing'] = 'Sleutel ontbreekt'; -$lang['admin']['dkim_add_key'] = 'Voeg ARC/DKIM-sleutel toe'; -$lang['admin']['dkim_keys'] = 'ARC/DKIM-sleutels'; +$lang['admin']['dkim_add_key'] = 'Voeg DKIM-sleutel toe'; +$lang['admin']['dkim_keys'] = 'DKIM-sleutels'; + $lang['admin']['dkim_private_key'] = 'Privรฉsleutel'; +$lang['admin']['dkim_domains_wo_keys'] = "Selecteer domeinen met ontbrekende sleutels"; +$lang['admin']['dkim_domains_selector'] = "Noemer"; $lang['admin']['add'] = 'Toevoegen'; $lang['add']['add_domain_restart'] = 'Voeg domein toe en herstart SOGo'; $lang['add']['add_domain_only'] = 'Voeg enkel domein toe'; @@ -467,52 +568,77 @@ $lang['admin']['active'] = 'Actief'; $lang['admin']['inactive'] = 'Inactief'; $lang['admin']['action'] = 'Handeling'; $lang['admin']['add_domain_admin'] = 'Voeg domeinbeheerder toe'; +$lang['admin']['add_admin'] = 'Voeg administrator toe'; $lang['admin']['add_settings_rule'] = 'Voeg regel toe'; $lang['admin']['rsetting_desc'] = 'Korte beschrijving'; $lang['admin']['rsetting_content'] = 'Regelinhoud'; $lang['admin']['rsetting_none'] = 'Geen regel beschikbaar'; $lang['admin']['rsetting_no_selection'] = 'Selecteer een regel'; -$lang['admin']['rsettings_preset_1'] = 'Zet alles uit voor geauthenticeerde gebruikers, behalve DKIM en ratelimiting'; +$lang['admin']['rsettings_preset_1'] = 'Schakel alles uit voor geauthenticeerde gebruikers, behalve DKIM en ratelimiting'; $lang['admin']['rsettings_preset_2'] = 'Postmeesters willen spam'; $lang['admin']['rsettings_insert_preset'] = 'Voeg voorbeeld "%s" in'; $lang['admin']['rsetting_add_rule'] = 'Voeg regel toe'; +$lang['admin']['queue_ays'] = 'Bevestig het verwijderen van alle onderdelen uit de wachtrij.'; +$lang['admin']['arrival_time'] = 'Aankomsttijd'; +$lang['admin']['message_size'] = 'Berichtgrootte'; +$lang['admin']['sender'] = 'Afzender'; +$lang['admin']['recipients'] = 'Ontvangers'; $lang['admin']['admin_domains'] = 'Domeintoewijzingen'; $lang['admin']['domain_admins'] = 'Domeinbeheerders'; +$lang['admin']['flush_queue'] = 'Leeg wachtrij'; +$lang['admin']['delete_queue'] = 'Verwijder alles'; +$lang['admin']['queue_deliver_mail'] = 'Lever af'; +$lang['admin']['queue_hold_mail'] = 'Houd vast'; +$lang['admin']['queue_unhold_mail'] = 'Geef vrij'; $lang['admin']['username'] = 'Gebruikersnaam'; $lang['admin']['edit'] = 'Wijzig'; $lang['admin']['remove'] = 'Verwijder'; $lang['admin']['save'] = 'Sla wijzigingen op'; -$lang['admin']['admin'] = 'Beheerder'; -$lang['admin']['admin_details'] = 'Wijzig beheerderdetails'; +$lang['admin']['admin'] = 'Administrator'; +$lang['admin']['admin_details'] = 'Toegangsinstellingen'; $lang['admin']['unchanged_if_empty'] = 'Laat leeg wanneer onveranderd'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'Toegang'; $lang['admin']['no_record'] = 'Geen vermelding'; $lang['admin']['filter_table'] = 'Filtertabel'; $lang['admin']['empty'] = 'Geen resultaten'; $lang['admin']['time'] = 'Tijd'; +$lang['admin']['last_applied'] = 'Voor het laatst toegepast'; +$lang['admin']['reset_limit'] = 'Verwijder hash'; +$lang['admin']['hash_remove_info'] = 'Het verwijderen van een ratelimit-hash, indien nog aanwezig, zal zijn teller volledig herstellen.<br>Elke hash wordt aangeduid met een aparte kleur.'; +$lang['warning']['hash_not_found'] = 'Hash niet gevonden'; +$lang['success']['hash_deleted'] = 'Hash verwijderd'; +$lang['admin']['authed_user'] = 'Geauthenticeerde gebruiker'; $lang['admin']['priority'] = 'Prioriteit'; $lang['admin']['message'] = 'Bericht'; +$lang['admin']['rate_name'] = 'Rate-naam'; $lang['admin']['refresh'] = 'Ververs'; -$lang['admin']['to_top'] = 'Terug naar boven'; +$lang['admin']['to_top'] = 'Naar boven'; $lang['admin']['in_use_by'] = 'In gebruik door'; $lang['admin']['forwarding_hosts'] = 'Doorstuurhosts'; -$lang['admin']['forwarding_hosts_hint'] = 'Inkomende berichten worden onvoorwaardelijk geaccepteerd vanaf iedere host hieronder vermeld. Deze hosts worden vervolgens niet gecontroleerd op DNSBLs of onderworpen aan greylisting. Spam ontvangen van hen wordt nooit geweigerd, maar het kan optioneel in de spam-map worden geplaatst. Het meest gebruikelijke gebruik hiervoor is het specificeren van mailservers waarop een regel is ingesteld die inkomende e-mails doorstuurt naar uw Mailcow-server.'; -$lang['admin']['forwarding_hosts_add_hint'] = 'Het is mogelijk om IPv4- of IPv6-adressen, netwerken in CIDR-notatie, hostnamen (die worden omgezet naar IP-adressen) of domeinnamen (die worden omgezet naar IP-adressen door SPF-records op te vragen of, bij gebrek daaraan, MX-records) op te geven.'; -$lang['admin']['relayhosts_hint'] = 'Stel relayhosts hier in om ze te kunnen selecteren in een domeinconfiguratiedialoog.'; -$lang['admin']['add_relayhost_add_hint'] = 'Wees ervan bewust dat relayhost authenticatiedata onversleuteld moeten worden opgeslagen.'; +$lang['admin']['forwarding_hosts_hint'] = 'Inkomende berichten worden onvoorwaardelijk geaccepteerd vanaf iedere host hieronder vermeld. Deze hosts worden hierdoor niet gecontroleerd op DNSBLs, en zullen de greylisting omzeilen. Spam wordt daarentegen zoals gebruikelijk in de spamfolder geplaatst. Dit wordt vaak gebruikt om mailservers te specificeren die mails doorsturen naar deze Mailcow-server.'; +$lang['admin']['forwarding_hosts_add_hint'] = 'Het is mogelijk om IPv4- of IPv6-adressen, netwerken in CIDR-notatie, hostnames (worden omgezet naar IP-adressen) of domeinnamen (worden tevens omgezet naar IP-adressen of, bij gebrek daaraan, MX-records) op te geven.'; +$lang['admin']['relayhosts_hint'] = 'Stel afzender-afhankelijke transportkaarten in om deze te kunnen gebruiken bij de configuratie van een domein.<br>De transportservice is altijd "smtp:". Er wordt rekening gehouden met het uitgaande versleutelingsbeleid van individuele gebruikers.'; +$lang['admin']['transports_hint'] = 'Een transportkaart wordt boven een afzender-afhankelijke transportkaart verkozen.<br>Het uitgaande versleutelingsbeleid van individuele gebruikers wordt genegeerd en kan enkel worden gehandhaafd doormiddel van globaal versleutelingsbeleid. De transportservice is altijd "smtp:".<br>Om de inloggegevens van een (voorbeeld) nexthop "[host]:25" te bepalen, zoekt Postfix <b>altijd</b> naar "nexthop" voodat er wordt gekeken naar "[nexthop]:25". Dit maakt het onmogelijk om "nexthop" en "[nexthop]:25" tegelijkertijd te gebruiken.'; +$lang['admin']['add_relayhost_hint'] = 'Wees ervan bewust dat de authenticatiedata onversleuteld wordt opgeslagen!'; +$lang['admin']['add_transports_hint'] = 'Wees ervan bewust dat de authenticatiedata onversleuteld wordt opgeslagen!'; $lang['admin']['host'] = 'Host'; $lang['admin']['source'] = 'Bron'; $lang['admin']['add_forwarding_host'] = 'Voeg doorstuurhost toe'; -$lang['admin']['add_relayhost'] = 'Voeg relayhost toe'; +$lang['admin']['add_relayhost'] = 'Voeg afzender-afhankelijke transportkaart toe'; +$lang['admin']['add_transport'] = 'Voeg transportkaart toe'; +$lang['admin']['relayhosts'] = 'Afzender-afhankelijke transportkaarten'; +$lang['admin']['transport_maps'] = 'Transportkaarten'; +$lang['admin']['routing'] = 'Routing'; +$lang['admin']['credentials_transport_warning'] = '<b>Waarschuwing</b>: Bij het toevoegen van een nieuwe transportkaart zullen de inloggegevens voor alle items met een overeenkomende nexthop-kolom worden overgeschreven.'; + +$lang['admin']['destination'] = 'Bestemming'; +$lang['admin']['nexthop'] = 'Nexthop'; $lang['success']['forwarding_host_removed'] = "Doorstuurhost %s is verwijderd"; $lang['success']['forwarding_host_added'] = "Doorstuurhost %s is toegevoegd"; -$lang['success']['relayhost_removed'] = "Relayhost %s is verwijderd"; -$lang['success']['relayhost_added'] = "Relayhost %s is toegevoegd"; +$lang['success']['relayhost_removed'] = "Invoer %s is verwijderd"; +$lang['success']['relayhost_added'] = "Invoer %s is toegevoegd"; $lang['diagnostics']['dns_records'] = 'DNS-vermeldingen'; -$lang['diagnostics']['dns_records_24hours'] = 'Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.'; -$lang['diagnostics']['dns_records_24hours'] = 'Houd er rekening mee dat veranderingen aan DNS tot wel 24 uur in beslag kunnen nemen voordat ze op deze pagina worden weergegeven. Het is bedoeld als een manier om gemakkelijk te zien hoe de DNS-vermeldingen zijn geconfigureerd en om te controleren of alle records correct zijn opgeslagen in DNS.'; +$lang['diagnostics']['dns_records_24hours'] = 'Houd er rekening mee dat veranderingen aan DNS tot wel 24 uur in beslag kunnen nemen voordat ze op deze pagina worden weergegeven. Deze informatie is bedoeld om gemakkelijk in te zien of de DNS-vermeldingen correct zijn geconfigureerd.'; $lang['diagnostics']['dns_records_name'] = 'Naam'; $lang['diagnostics']['dns_records_type'] = 'Type'; $lang['diagnostics']['dns_records_data'] = 'Correcte gegevens'; @@ -526,27 +652,40 @@ $lang['admin']['api_allow_from'] = "Sta API-toegang toe vanaf deze IP-adressen"; $lang['admin']['api_key'] = "API-sleutel"; $lang['admin']['activate_api'] = "Activeer API"; $lang['admin']['regen_api_key'] = "Vernieuw API-sleutel"; -$lang['admin']['ban_list_info'] = "Bekijk een lijst met verbannen IP-adressen hieronder: <b>netwerk (resterende tijd) - [actions]</b>.<br />IP-adressen gemarkeerd om te worden toegestaan, zullen worden verwijderd van de verbanningslijst binnen enkele seconden.<br />Rode labels geven een permanente verbanning aan."; +$lang['admin']['ban_list_info'] = "Bekijk de lijst met verbannen IP-adressen hieronder: <b>netwerk (resterende tijd) - [acties]</b>.<br />Rode labels geven een permanente verbanning aan.<br />Het kan enkele seconden duren voordat wijzigingen hieronder zichtbaar zijn."; $lang['admin']['unban_pending'] = "bezig met toestaan"; $lang['admin']['queue_unban'] = "markeer om toe te staan"; $lang['admin']['no_active_bans'] = "Geen actieve verbanningen"; $lang['admin']['quarantine'] = "Quarantaine"; -$lang['admin']['quarantine_retention_size'] = "Retenties per postvak<br />0 betekent <b>inactief</b>!"; -$lang['admin']['quarantine_max_size'] = "Max. grootte in MiB (grotere elementen worden verwijderd)<br />0 betekent <b>niet</b> onbeperkt!"; -$lang['admin']['quarantine_exclude_domains'] = "Sluit domeinen en aliasdomeinen uit:"; - -$lang['admin']['ui_texts'] = "UI-labels en teksten"; +$lang['admin']['rspamd_settings_map'] = "Rspamd-instellingen"; +$lang['admin']['quota_notifications'] = "Quotum-notificaties"; +$lang['admin']['quota_notifications_vars'] = "{{percent}} toont het huidige quotum van van de gebruiker<br>{{username}} staat voor de naam van het desbetreffende postvak"; +$lang['admin']['active_rspamd_settings_map'] = "Huidige instellingen"; +$lang['admin']['quota_notifications_info'] = "Quotum-notificaties worden verstuurd naar gebruikers wanneer deze 80% of 95% van hun opslagcapaciteit overschreden hebben."; +$lang['admin']['quarantine_retention_size'] = "Maximale retenties per postvak:<br><small>Gebruik 0 om deze functionaliteit <b>uit te schakelen</b>.</small>"; +$lang['admin']['quarantine_max_size'] = "Maximale grootte in MiB (mail die de limiet overschrijdt zal worden verwijderd):<br><small>0 betekent <b>niet</b> onbeperkt!</small>"; +$lang['admin']['quarantine_exclude_domains'] = "Sluit de volgende domeinen en aliasdomeinen uit"; +$lang['admin']['quarantine_release_format'] = "Verstuur vrijgegeven items als"; +$lang['admin']['quarantine_release_format_raw'] = "Origineel"; +$lang['admin']['quarantine_release_format_att'] = "Bijlage"; +$lang['admin']['quarantine_notification_sender'] = "Afzender van notificaties"; +$lang['admin']['quarantine_notification_subject'] = "Onderwerp van notificaties"; +$lang['admin']['quarantine_notification_html'] = "Notificatiesjabloon:<br><small>Laat leeg om de standaardsjabloon te herstellen.</small>"; +$lang['admin']['quota_notification_sender'] = "Afzender van notificaties"; +$lang['admin']['quota_notification_subject'] = "Onderwerp van notificaties"; +$lang['admin']['quota_notification_html'] = "Notificatiesjabloon:<br><small>Laat leeg om de standaardsjabloon te herstellen.</small>"; +$lang['admin']['ui_texts'] = "Labels en teksten"; $lang['admin']['help_text'] = "Pas hulpteksten onder inlogvenster aan (HTML toegestaan)"; -$lang['admin']['title_name'] = '"Mailcow UI" website-titel'; -$lang['admin']['main_name'] = '"Mailcow UI" naam'; -$lang['admin']['apps_name'] = '"Mailcow Apps" naam'; +$lang['admin']['title_name'] = '"Mailcow" (website-titel)'; +$lang['admin']['main_name'] = '"Mailcow"'; +$lang['admin']['apps_name'] = '"Mailcow-apps"'; -$lang['admin']['customize'] = "Uiterlijk aanpassen"; -$lang['admin']['change_logo'] = "Verander logo"; -$lang['admin']['logo_info'] = "Het plaatje zal worden geschaald naar een hoogte van 40px voor de navigatiebar en een breedte van 250px voor de startpagina."; +$lang['admin']['customize'] = "Uiterlijk"; +$lang['admin']['change_logo'] = "Logo"; +$lang['admin']['logo_info'] = "De afbeelding zal worden geschaald naar een hoogte van 40px voor de navigatiebar, en naar een breedte van 250px voor de startpagina."; $lang['admin']['upload'] = "Upload"; -$lang['admin']['app_links'] = "App links"; +$lang['admin']['app_links'] = "Applicatielinks"; $lang['admin']['app_name'] = "Naam"; $lang['admin']['link'] = "Link"; $lang['admin']['remove_row'] = "Verwijder rij"; @@ -556,11 +695,15 @@ $lang['admin']['merged_vars_hint'] = 'Grijze rijen zijn samengevoegd van <code>v $lang['mailbox']['waiting'] = "Wachten"; $lang['mailbox']['status'] = "Status"; $lang['mailbox']['running'] = "Wordt uitgevoerd"; +$lang['mailbox']['enable_x'] = "Schakel in"; +$lang['mailbox']['disable_x'] = "Schakel uit"; $lang['edit']['spam_score'] = "Stel een aangepaste spamscore in"; +$lang['user']['spam_score_reset'] = "Herstel naar standaardwaarde"; $lang['edit']['spam_policy'] = "Voeg onderdelen toe, of verwijder onderdelen van de witte en zwarte lijst"; $lang['edit']['spam_alias'] = "Maak een nieuw tijdelijk alias aan, of pas deze aan"; +$lang['danger']['comment_too_long'] = "Opmerkingen mogen niet langer dan 160 karakters zijn"; $lang['danger']['img_tmp_missing'] = "Kan afbeelding niet valideren, tijdelijk bestand niet gevonden"; $lang['danger']['img_invalid'] = "Kan afbeelding niet valideren"; $lang['danger']['invalid_mime_type'] = "Ongeldig mime-type"; @@ -571,11 +714,10 @@ $lang['success']['reset_main_logo'] = "Het standaardlogo is hersteld"; $lang['success']['items_released'] = "Geselecteerde onderdelen zijn vrijgegeven"; $lang['success']['item_released'] = "Onderdeel %s vrijgegeven"; $lang['danger']['imagick_exception'] = "Error: Er is een probleem opgetreden met Imagick tijdens het lezen van de afbeelding"; - $lang['quarantine']['quarantine'] = "Quarantaine"; $lang['quarantine']['learn_spam_delete'] = "Onthoud als spam en verwijder"; -$lang['quarantine']['qinfo'] = 'Het quarantainesysteem slaat geweigerde e-mail op, terwijl het voor de afzender als <em>niet</em> ontvangen bestempeld is.<br>"' . $lang['quarantine']['learn_spam_delete'] . '" traint het systeem om toekomstige soortgelijke e-mails direct als spam te classificeren.<br>Wees er van bewust dat wanneer er meerdere berichten worden onderzocht, dit mogelijk enige tijd kan duren.'; -$lang['quarantine']['release'] = "Vrijgeven"; +$lang['quarantine']['qinfo'] = 'Het quarantainesysteem slaat geweigerde e-mail op, terwijl het voor de afzender lijkt alsof deze <em>niet</em> ontvangen is.<br>"' . $lang['quarantine']['learn_spam_delete'] . '" traint het systeem om soortgelijke e-mails in de toekomst weer als spam te markeren.<br>Wees er van bewust dat wanneer er meerdere berichten worden onderzocht, dit mogelijk enige tijd kan duren.'; +$lang['quarantine']['release'] = "Geef vrij"; $lang['quarantine']['empty'] = 'Geen resultaten'; $lang['quarantine']['toggle_all'] = 'Selecteer alles'; $lang['quarantine']['quick_actions'] = 'Handelingen'; @@ -592,20 +734,38 @@ $lang['quarantine']['subj'] = "Onderwerp"; $lang['quarantine']['text_plain_content'] = "Inhoud (tekst)"; $lang['quarantine']['text_from_html_content'] = "Inhoud (geconverteerde html)"; $lang['quarantine']['atts'] = "Bijlagen"; -$lang['danger']['fuzzy_learn_error'] = "Fuzzy hash training-fout: %s"; +$lang['quarantine']['low_danger'] = "Laag risico"; +$lang['quarantine']['neutral_danger'] = "Neutraal/geen beoordeling"; +$lang['quarantine']['medium_danger'] = "Middelmatig risico"; +$lang['quarantine']['high_danger'] = "Hoog risico"; +$lang['quarantine']['danger'] = "Risico"; +$lang['quarantine']['confirm_delete'] = "Bevestig de verwijdering van dit item."; +$lang['quarantine']['qhandler_success'] = "Verzoek met succes verzonden naar het systeem. Je kunt het venster nu veilig sluiten."; + +$lang['warning']['fuzzy_learn_error'] = "Fuzzy hash training-fout: %s"; $lang['danger']['spam_learn_error'] = "Spamtraining-fout: %s"; -$lang['success']['qlearn_spam'] = "Bericht %s werd als spam geclassificeerd en is verwijderd"; +$lang['success']['qlearn_spam'] = "Bericht %s werd als spam gemarkeerd en is verwijderd"; -$lang['header']['quarantine'] = "Quarantaine"; -$lang['header']['debug'] = "Debug"; - -$lang['debug']['log_info'] = '<p>Mailcow <b>in-geheugen logs</b> worden verzameld in Redis-lijsten en worden elke minuut bijgesneden naar LOG_LINES (%d) om de stabiliteit te garanderen.<br>Logs in-geheugen zijn niet bedoeld om te blijven staan. Alle applicaties die in-geheugen loggen, worden ook naar het Docker-proces gelogd.<br>De in-geheugen logs zouden gebruikt moeten worden voor het oplossen van kleine problemen met containers.</p><p><b>Externe logs</b> worden verzameld doormiddel van de API van de applicaties.</p><p><b>Statische logs</b> zijn over het algemeen activiteitenlogs, die niet naar het Docker-proces worden gestuurd</p>'; +$lang['debug']['system_containers'] = 'Systeem & containers'; +$lang['debug']['solr_status'] = 'Status van Solr'; +$lang['debug']['solr_dead'] = 'Solr is uitgeschakeld, uitgevallen of nog bezig met opstarten.'; +$lang['debug']['logs'] = 'Logs'; +$lang['debug']['log_info'] = '<p>Mailcows <b>geheugenlogs</b> worden elke minuut afgesneden naar maximaal %d regels om de stabiliteit te garanderen.<br>Geheugenlogs zijn niet bedoeld om opgeslagen te worden. Alle applicaties die geheugenlogs verzamelen, worden ook naar het Docker-proces gelogd.<br>De geheugenlogs kunnen gebruikt worden voor het oplossen van kleine problemen met specifieke containers.</p><p><b>Externe logs</b> worden verzameld doormiddel van de API van deze applicaties.</p><p><b>Statische logs</b> zijn activiteitenlogs. Deze worden niet naar het Docker-proces gestuurd.</p>'; -$lang['debug']['in_memory_logs'] = 'Logs in-geheugen'; +$lang['debug']['in_memory_logs'] = 'Geheugenlogs'; $lang['debug']['external_logs'] = 'Externe logs'; $lang['debug']['static_logs'] = 'Statische logs'; +$lang['debug']['solr_uptime'] = 'Uptime'; +$lang['debug']['solr_started_at'] = 'Opgestart op'; +$lang['debug']['solr_last_modified'] = 'Voor het laatst bijgewerkt op'; +$lang['debug']['solr_size'] = 'Grootte'; +$lang['debug']['solr_docs'] = 'Documenten'; -$lang['quarantine']['release_body'] = "We hebben het oorspronkelijke bericht als eml-bestand meegestuurd."; +$lang['debug']['disk_usage'] = 'Schijfgebruik'; +$lang['debug']['containers_info'] = "Containerinformatie"; +$lang['debug']['restart_container'] = 'Herstart'; + +$lang['quarantine']['release_body'] = "We hebben het oorspronkelijke bericht als los bestand meegestuurd. Klik erop om deze weer te geven."; $lang['danger']['release_send_failed'] = "Het volgende bericht kon niet worden vrijgegeven: %s"; $lang['quarantine']['release_subject'] = "Mogelijk schadelijk quarantaine-item %s"; @@ -624,8 +784,9 @@ $lang['mailbox']['bcc_maps'] = "BCC-kaarten"; $lang['mailbox']['bcc_to_sender'] = "Schakel over naar afzenderkaarten"; $lang['mailbox']['bcc_to_rcpt'] = "Schakel over naar ontvangerkaarten"; $lang['mailbox']['add_bcc_entry'] = "Voeg BCC-kaart toe"; -$lang['mailbox']['bcc_info'] = "BCC-kaarten worden gebruikt om kopieรซn van alle berichten naar een ander adres door te sturen. Een ontvangerkaart wordt gebruikt zodra de lokale bestemming fungeert als ontvanger van een mail. Afzenderkaarten werken conform het zelfde principe.<br>De lokale bestemming zal niet worden geรฏnformeerd bij een mislukte aflevering."; -$lang['mailbox']['address_rewriting'] = 'Adres omleiden'; +$lang['mailbox']['add_tls_policy_map'] = "Voeg versleutelingsbeleid toe"; +$lang['mailbox']['bcc_info'] = "BCC-kaarten worden gebruikt om kopieรซn van alle berichten naar een ander adres door te sturen.<br>Wees er van bewust dat er geen melding wordt gedaan van een mislukte aflevering."; +$lang['mailbox']['address_rewriting'] = 'Adresomleidingen'; $lang['mailbox']['recipient_maps'] = 'Ontvangerkaarten'; $lang['mailbox']['recipient_map'] = 'Ontvangerkaart'; $lang['mailbox']['recipient_map_info'] = 'Ontvangerkaarten worden gebruikt om het doeladres van een bericht te vervangen voordat het in een postvak terecht komt.'; @@ -635,10 +796,14 @@ $lang['mailbox']['recipient_map_old'] = 'Oorspronkelijke ontvanger'; $lang['mailbox']['recipient_map_new'] = 'Nieuwe ontvanger'; $lang['danger']['invalid_recipient_map_new'] = 'Ongeldige nieuwe ontvanger ingevoerd: %s'; $lang['danger']['invalid_recipient_map_old'] = 'Ongeldige oorspronkelijke ontvanger ingevoerd: %s'; -$lang['danger']['recipient_map_entry_exists'] = 'Een ontvangerkaart voor %s bestaat al'; -$lang['success']['recipient_map_entry_saved'] = 'Ontvangerkaart voor %s is opgeslagen'; -$lang['success']['recipient_map_entry_deleted'] = 'Ontvangerkaart voor %s is verwijderd'; +$lang['danger']['recipient_map_entry_exists'] = 'Ontvangerkaart met "%s" bestaat reeds'; +$lang['success']['recipient_map_entry_saved'] = 'Ontvangerkaart %s is opgeslagen'; +$lang['success']['recipient_map_entry_deleted'] = 'Ontvangerkaart %s is verwijderd'; +$lang['danger']['tls_policy_map_entry_exists'] = 'Versleutelingsbeleid met "%s" bestaat reeds'; +$lang['success']['tls_policy_map_entry_saved'] = 'Versleutelingsbeleid "%s" is opgeslagen'; +$lang['success']['tls_policy_map_entry_deleted'] = 'Versleutelingsbeleid %s is verwijderd'; $lang['mailbox']['add_recipient_map_entry'] = 'Voeg ontvangerkaart toe'; +$lang['danger']['tls_policy_map_parameter_invalid'] = "Beleidsparameter is ongeldig"; $lang['oauth2']['scope_ask_permission'] = 'Een applicatie heeft toegang tot de volgende onderdelen gevraagd'; $lang['oauth2']['profile'] = 'Profiel'; @@ -647,3 +812,20 @@ $lang['oauth2']['permit'] = 'Authoriseer applicatie'; $lang['oauth2']['authorize_app'] = 'Authoriseer applicatie'; $lang['oauth2']['deny'] = 'Weiger'; $lang['oauth2']['access_denied'] = 'Log in als een postvakeigenaar om OAuth-toegang te verlenen'; + +$lang['admin']['sys_mails'] = 'Systeemmails'; +$lang['admin']['subject'] = 'Onderwerp'; +$lang['admin']['from'] = 'Afzender'; +$lang['admin']['include_exclude'] = 'Ontvangers'; +$lang['admin']['include_exclude_info'] = 'Zonder selectie worden <b>alle postvakken</b> benaderd!'; +$lang['admin']['excludes'] = 'Exclusief'; +$lang['admin']['includes'] = 'Inclusief'; +$lang['admin']['text'] = 'Tekst'; +$lang['admin']['activate_send'] = 'Bevestig bovenstaande gegevens'; +$lang['admin']['send'] = 'Verstuur'; + +$lang['warning']['ip_invalid'] = 'Ongeldig IP overgeslagen: %s'; +$lang['danger']['text_empty'] = 'De tekst dient ingevuld te worden'; +$lang['danger']['subject_empty'] = 'Het onderwerp dient ingevuld te worden'; +$lang['danger']['from_invalid'] = 'De afzender dient ingevuld te worden'; +$lang['danger']['network_host_invalid'] = 'Ongeldig netwerk of host: %s'; diff --git a/data/web/lang/lang.pl.php b/data/web/lang/lang.pl.php index adc4edab..140b0c32 100644 --- a/data/web/lang/lang.pl.php +++ b/data/web/lang/lang.pl.php @@ -193,8 +193,6 @@ $lang['mailbox']['target_address'] = 'Adres Idลบ do'; $lang['mailbox']['username'] = 'Nazwa uลผytkownika'; $lang['mailbox']['fname'] = 'Peลna nazwa'; $lang['mailbox']['filter_table'] = 'Tabela filtru'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'W uลผyciu (%)'; $lang['mailbox']['msg_num'] = 'Wiadomoลฤ #'; $lang['mailbox']['remove'] = 'Usuล'; @@ -360,8 +358,6 @@ $lang['admin']['save'] = 'Zapisz zmiany'; $lang['admin']['admin'] = 'Administrator'; $lang['admin']['admin_details'] = 'Edytuj szczegรณลy administratora'; $lang['admin']['unchanged_if_empty'] = 'W przypadku braku zmian, nie wypeลniaj'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'Dostฤp'; $lang['admin']['no_record'] = 'Brak rekordu'; $lang['admin']['filter_table'] = 'Tabela filtru'; diff --git a/data/web/lang/lang.pt.php b/data/web/lang/lang.pt.php index 13a86300..d6fc8649 100644 --- a/data/web/lang/lang.pt.php +++ b/data/web/lang/lang.pt.php @@ -140,8 +140,6 @@ $lang['mailbox']['target_address'] = 'Encaminhar para'; $lang['mailbox']['username'] = 'Usuรกrio'; $lang['mailbox']['fname'] = 'Nome'; $lang['mailbox']['filter_table'] = 'Procurar'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'Em uso (%)'; $lang['mailbox']['msg_num'] = 'Mensagens'; $lang['mailbox']['remove'] = 'Remover'; @@ -239,7 +237,5 @@ $lang['admin']['save'] = 'Salvar'; $lang['admin']['admin'] = 'Administrador'; $lang['admin']['admin_details'] = 'Editar informaรงรตes do administrator'; $lang['admin']['unchanged_if_empty'] = 'Deixar em branco para nรฃo alterar'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'Acessos'; $lang['admin']['no_record'] = 'Nenhum registro'; diff --git a/data/web/lang/lang.ru.php b/data/web/lang/lang.ru.php index fa2074f6..10a2b30c 100644 --- a/data/web/lang/lang.ru.php +++ b/data/web/lang/lang.ru.php @@ -189,8 +189,6 @@ $lang['mailbox']['target_address'] = 'ะัะฝะพะฒะฝะพะน ะฐะดัะตั'; $lang['mailbox']['username'] = 'ะะผั ะฟะพะปัะทะพะฒะฐัะตะปั'; $lang['mailbox']['fname'] = 'ะะพะปะฝะพะต ะธะผั'; $lang['mailbox']['filter_table'] = 'ะะพะธัะบ'; -$lang['mailbox']['yes'] = '✔'; -$lang['mailbox']['no'] = '✘'; $lang['mailbox']['in_use'] = 'ะัะฟะพะปัะทะพะฒะฐะฝะพ (%)'; $lang['mailbox']['msg_num'] = 'ะะธััะผะฐ #'; $lang['mailbox']['remove'] = 'ะฃะดะฐะปะธัั'; @@ -359,8 +357,6 @@ $lang['admin']['save'] = 'ะกะพั ัะฐะฝะธัั ะธะทะผะตะฝะตะฝะธั'; $lang['admin']['admin'] = 'ะะดะผะธะฝะธัััะฐัะพั'; $lang['admin']['admin_details'] = 'ะะทะผะตะฝะธัั ะดะฐะฝะฝัะต ะฐะดะผะธะฝะธัััะฐัะพัะฐ'; $lang['admin']['unchanged_if_empty'] = 'ะัะปะธ ะฑะตะท ะธะทะผะตะฝะตะฝะธะน ะพััะฐะฒััะต ะฟััััะผ'; -$lang['admin']['yes'] = '✔'; -$lang['admin']['no'] = '✘'; $lang['admin']['access'] = 'ะะพัััะฟ ะบ'; $lang['admin']['no_record'] = 'ะะตั ะทะฐะฟะธัะตะน'; $lang['admin']['filter_table'] = 'ะะพะธัะบ'; diff --git a/data/web/mailbox.php b/data/web/mailbox.php index e236a8af..392e9adf 100644 --- a/data/web/mailbox.php +++ b/data/web/mailbox.php @@ -1,8 +1,8 @@ <?php -require_once "inc/prerequisites.inc.php"; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) { -require_once "inc/header.inc.php"; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; ?> <div class="container"> @@ -22,6 +22,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <li role="presentation"><a href="#tab-syncjobs" aria-controls="tab-syncjobs" role="tab" data-toggle="tab"><?=$lang['mailbox']['sync_jobs'];?></a></li> <li role="presentation"><a href="#tab-filters" aria-controls="tab-filters" role="tab" data-toggle="tab"><?=$lang['mailbox']['filters'];?></a></li> <li role="presentation"><a href="#tab-bcc" aria-controls="tab-filters" role="tab" data-toggle="tab"><?=$lang['mailbox']['address_rewriting'];?></a></li> + <li role="presentation"<?=($_SESSION['mailcow_cc_role'] == "admin") ?: ' class="hidden"';?>><a href="#tab-tls-policy" aria-controls="tab-tls-policy" role="tab" data-toggle="tab"><?=$lang['mailbox']['tls_policy_maps'];?></a></li> </ul> <div class="row"> @@ -44,10 +45,10 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> <? if($_SESSION['mailcow_cc_role'] == "admin"): ?> - <li><a id="edit_selected" data-id="domain" data-api-url='edit/domain' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="domain" data-api-url='edit/domain' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="domain" data-api-url='edit/domain' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="domain" data-api-url='edit/domain' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="domain" data-api-url='delete/domain' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="domain" data-api-url='delete/domain' href="#"><?=$lang['mailbox']['remove'];?></a></li> <? endif; ?> </ul> <? if($_SESSION['mailcow_cc_role'] == "admin"): ?> @@ -74,10 +75,24 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li class="dropdown-header"><?=$lang['mailbox']['mailbox'];?></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="delete_selected" data-id="mailbox" data-api-url='delete/mailbox' href="#"><?=$lang['mailbox']['remove'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="mailbox" data-api-url='delete/mailbox' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li class="dropdown-header"><?=$lang['mailbox']['tls_enforce_in'];?></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/tls_policy' data-api-attr='{"tls_enforce_in":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/tls_policy' data-api-attr='{"tls_enforce_in":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li role="separator" class="divider"></li> + <li class="dropdown-header"><?=$lang['mailbox']['tls_enforce_out'];?></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/tls_policy' data-api-attr='{"tls_enforce_out":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/tls_policy' data-api-attr='{"tls_enforce_out":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li role="separator" class="divider"></li> + <li class="dropdown-header"><?=$lang['mailbox']['quarantine_notification'];?></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"hourly"}' href="#"><?=$lang['user']['hourly'];?></a></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"daily"}' href="#"><?=$lang['user']['daily'];?></a></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"weekly"}' href="#"><?=$lang['user']['weekly'];?></a></li> + <li><a data-action="edit_selected" data-id="mailbox" data-api-url='edit/quarantine_notification' data-api-attr='{"quarantine_notification":"never"}' href="#"><?=$lang['user']['never'];?></a></li> </ul> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addMailboxModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_mailbox'];?></a> </div> @@ -101,10 +116,10 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="resource" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="resource" data-api-url='edit/resource' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="resource" data-api-url='edit/resource' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="resource" data-api-url='edit/resource' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="resource" data-api-url='edit/resource' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="resource" data-api-url='delete/resource' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="resource" data-api-url='delete/resource' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addResourceModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_resource'];?></a> </div> @@ -134,12 +149,12 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="alias-domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="alias-domain" data-api-url='edit/alias-domain' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="alias-domain" data-api-url='edit/alias-domain' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="alias-domain" data-api-url='edit/alias-domain' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="alias-domain" data-api-url='edit/alias-domain' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="alias-domain" data-api-url='delete/alias-domain' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="alias-domain" data-api-url='delete/alias-domain' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> - <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addAliasDomainModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_domain_alias'];?></a> + <a class="btn btn-sm btn-success" href="#" data-acl="<?=$_SESSION['acl']['alias_domains'];?>" data-toggle="modal" data-target="#addAliasDomainModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_domain_alias'];?></a> </div> </div> </div> @@ -161,10 +176,10 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="alias" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="alias" data-api-url='edit/alias' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="alias" data-api-url='delete/alias' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="alias" data-api-url='delete/alias' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addAliasModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_alias'];?></a> </div> @@ -184,16 +199,16 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <table class="table table-striped" id="sync_job_table"></table> </div> <div class="mass-actions-mailbox"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['syncjobs'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"last_run":""}' href="#"><?=$lang['mailbox']['last_run_reset'];?></a></li> + <li><a data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"last_run":""}' href="#"><?=$lang['mailbox']['last_run_reset'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-id="syncjob" data-api-url='delete/syncjob' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a> </div> @@ -214,17 +229,17 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <table class="table table-striped" id="filter_table"></table> </div> <div class="mass-actions-mailbox"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['filters'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="filter_item" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"prefilter"}' href="#"><?=$lang['mailbox']['set_prefilter'];?></a></li> - <li><a id="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"postfilter"}' href="#"><?=$lang['mailbox']['set_postfilter'];?></a></li> + <li><a data-action="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"prefilter"}' href="#"><?=$lang['mailbox']['set_prefilter'];?></a></li> + <li><a data-action="edit_selected" data-id="filter_item" data-api-url='edit/filter' data-api-attr='{"filter_type":"postfilter"}' href="#"><?=$lang['mailbox']['set_postfilter'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-id="filter_item" data-api-url='delete/filter' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-id="filter_item" data-api-url='delete/filter' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addFilterModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_filter'];?></a> </div> @@ -245,23 +260,23 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <table class="table table-striped" id="bcc_table"></table> </div> <div class="mass-actions-mailbox"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['bcc_maps'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="bcc" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"sender"}' href="#"><?=$lang['mailbox']['bcc_to_sender'];?></a></li> - <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#"><?=$lang['mailbox']['bcc_to_rcpt'];?></a></li> + <li><a data-action="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"sender"}' href="#"><?=$lang['mailbox']['bcc_to_sender'];?></a></li> + <li><a data-action="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#"><?=$lang['mailbox']['bcc_to_rcpt'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="bcc" data-api-url='delete/bcc' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="bcc" data-api-url='delete/bcc' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addBCCModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_bcc_entry'];?></a> </div> </div> </div> - <div class="panel panel-default"> + <div class="panel panel-default <?=($_SESSION['mailcow_cc_role'] == "admin") ?: 'hidden';?>"> <div class="panel-heading"> <?=$lang['mailbox']['recipient_maps'];?> <span class="badge badge-info table-lines"></span> <div class="btn-group pull-right"> @@ -272,27 +287,49 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <div class="table-responsive"> <table class="table table-striped" id="recipient_map_table"></table> </div> -<?php -if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin")) - $display = 'block'; -else - $display = 'none'; -?> <div class="mass-actions-mailbox" style="display: <?php echo $display; ?>"> <div class="btn-group"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="recipient_map" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="recipient_map" data-api-url='edit/recipient_map' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="recipient_map" data-api-url='edit/recipient_map' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="recipient_map" data-api-url='edit/recipient_map' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="recipient_map" data-api-url='edit/recipient_map' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="recipient_map" data-api-url='delete/recipient_map' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="recipient_map" data-api-url='delete/recipient_map' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addRecipientMapModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_recipient_map_entry'];?></a> </div> </div> </div> </div> + + <div role="tabpanel" class="tab-pane <?=($_SESSION['mailcow_cc_role'] == "admin") ?: 'hidden';?>" id="tab-tls-policy"> + <div class="panel panel-default"> + <div class="panel-heading"> + <?=$lang['mailbox']['tls_policy_maps_long'];?> <span class="badge badge-info table-lines"></span> + <div class="btn-group pull-right"> + <button class="btn btn-xs btn-default refresh_table" data-draw="draw_tls_policy_table" data-table="tls_policy_table"><?=$lang['admin']['refresh'];?></button> + </div> + </div> + <p style="margin:10px" class="help-block"><?=$lang['mailbox']['tls_policy_maps_info'];?></p> + <div class="table-responsive"> + <table class="table table-striped" id="tls_policy_table"></table> + </div> + <div class="mass-actions-mailbox"> + <div class="btn-group"> + <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="tls-policy-map" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> + <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> + <ul class="dropdown-menu"> + <li><a data-action="edit_selected" data-id="tls-policy-map" data-api-url='edit/tls-policy-map' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="tls-policy-map" data-api-url='edit/tls-policy-map' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li role="separator" class="divider"></li> + <li><a data-action="delete_selected" data-id="tls-policy-map" data-api-url='delete/tls-policy-map' href="#"><?=$lang['mailbox']['remove'];?></a></li> + </ul> + <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addTLSPolicyMapAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_tls_policy_map'];?></a> + </div> + </div> + </div> + </div> </div> <!-- /tab-content --> </div> <!-- /col-md-12 --> </div> <!-- /row --> @@ -304,17 +341,25 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/mailbox.php'; <?php $lang_mailbox = json_encode($lang['mailbox']); echo "var lang = ". $lang_mailbox . ";\n"; +echo "var acl = '". json_encode($_SESSION['acl']) . "';\n"; echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n"; $role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin'; +$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false'; echo "var role = '". $role . "';\n"; +echo "var is_dual = " . $is_dual . ";\n"; echo "var pagination_size = '". $PAGINATION_SIZE . "';\n"; +$ALLOW_ADMIN_EMAIL_LOGIN = (preg_match( + "/^([yY][eE][sS]|[yY])+$/", + $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"] +)) ? "true" : "false"; +echo "var ALLOW_ADMIN_EMAIL_LOGIN = " . $ALLOW_ADMIN_EMAIL_LOGIN . ";\n"; ?> </script> -<script src="js/footable.min.js"></script> -<script src="js/mailbox.js"></script> <?php +$js_minifier->add('/web/js/site/mailbox.js'); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; -} else { +} +else { header('Location: /'); exit(); } diff --git a/data/web/mobileconfig.php b/data/web/mobileconfig.php index 98691c55..ade4f606 100644 --- a/data/web/mobileconfig.php +++ b/data/web/mobileconfig.php @@ -12,158 +12,163 @@ if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != 'use error_reporting(0); header('Content-Type: application/x-apple-aspen-config'); -header('Content-Disposition: attachment; filename="Mailcow.mobileconfig"'); +header('Content-Disposition: attachment; filename="'.$UI_TEXTS['main_name'].'.mobileconfig"'); $email = $_SESSION['mailcow_cc_username']; $domain = explode('@', $_SESSION['mailcow_cc_username'])[1]; -$identifier = implode('.', array_reverse(explode('.', $domain))) . '.iphoneprofile.mailcow'; +$identifier = implode('.', array_reverse(preg_split( '/(@|\.)/', $email))) . '.appleprofile.'.preg_replace('/[^a-zA-Z0-9]+/', '', $UI_TEXTS['main_name']); try { $stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); $stmt->execute(array(':username' => $email)); $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); + $displayname = empty($MailboxData['name']) ? $email : $MailboxData['name']; } catch(PDOException $e) { - die("Failed to determine name from SQL"); -} -if (!empty($MailboxData['name'])) { - $displayname = $MailboxData['name']; -} -else { $displayname = $email; } +if (isset($_GET['only_email'])) { + $onlyEmailAccount = true; + $description = 'IMAP'; +} else { + $onlyEmailAccount = false; + $description = 'IMAP, CalDAV, CardDAV'; +} + echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n"; ?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> -<dict> - <key>PayloadContent</key> - <array> - <dict> - <key>CalDAVAccountDescription</key> - <string><?php echo $domain; ?></string> - <key>CalDAVHostName</key> - <string><?php echo $autodiscover_config['caldav']['server']; ?></string> - <key>CalDAVPort</key> - <real><?php echo $autodiscover_config['caldav']['port']; ?></real> - <key>CalDAVPrincipalURL</key> - <string>/SOGo/dav/<?php echo $email; ?></string> - <key>CalDAVUseSSL</key> - <true/> - <key>CalDAVUsername</key> - <string><?php echo $email; ?></string> - <key>PayloadDescription</key> - <string>Configures CalDAV account.</string> - <key>PayloadDisplayName</key> - <string>CalDAV (<?php echo $domain; ?>)</string> - <key>PayloadIdentifier</key> - <string><?php echo $identifier; ?>.CalDAV</string> - <key>PayloadOrganization</key> - <string></string> - <key>PayloadType</key> - <string>com.apple.caldav.account</string> - <key>PayloadUUID</key> - <string>FC898573-EBA8-48AF-93BD-BFA0C9778FA7</string> - <key>PayloadVersion</key> - <integer>1</integer> - </dict> - <dict> - <key>EmailAccountDescription</key> - <string><?php echo $domain; ?></string> - <key>EmailAccountType</key> - <string>EmailTypeIMAP</string> - <key>EmailAccountName</key> - <string><?php echo $displayname; ?></string> - <key>EmailAddress</key> - <string><?php echo $email; ?></string> - <key>IncomingMailServerAuthentication</key> - <string>EmailAuthPassword</string> - <key>IncomingMailServerHostName</key> - <string><?php echo $autodiscover_config['imap']['server']; ?></string> - <key>IncomingMailServerPortNumber</key> - <integer><?php echo $autodiscover_config['imap']['port']; ?></integer> - <key>IncomingMailServerUseSSL</key> - <true/> - <key>IncomingMailServerUsername</key> - <string><?php echo $email; ?></string> - <key>OutgoingMailServerAuthentication</key> - <string>EmailAuthPassword</string> - <key>OutgoingMailServerHostName</key> - <string><?php echo $autodiscover_config['smtp']['server']; ?></string> - <key>OutgoingMailServerPortNumber</key> - <integer><?php echo $autodiscover_config['smtp']['port']; ?></integer> - <key>OutgoingMailServerUseSSL</key> - <true/> - <key>OutgoingMailServerUsername</key> - <string><?php echo $email; ?></string> - <key>OutgoingPasswordSameAsIncomingPassword</key> - <true/> - <key>PayloadDescription</key> - <string>Configures email account.</string> - <key>PayloadDisplayName</key> - <string>IMAP Account (<?php echo $domain; ?>)</string> - <key>PayloadIdentifier</key> - <string><?php echo $identifier; ?>.email</string> - <key>PayloadOrganization</key> - <string></string> - <key>PayloadType</key> - <string>com.apple.mail.managed</string> - <key>PayloadUUID</key> - <string>00294FBB-1016-413E-87B9-652D856D6875</string> - <key>PayloadVersion</key> - <integer>1</integer> - <key>PreventAppSheet</key> - <false/> - <key>PreventMove</key> - <false/> - <key>SMIMEEnabled</key> - <false/> - </dict> - <dict> - <key>CardDAVAccountDescription</key> - <string><?php echo $domain; ?></string> - <key>CardDAVHostName</key> - <string><?php echo $autodiscover_config['carddav']['server']; ?></string> - <key>CardDAVPort</key> - <integer><?php echo $autodiscover_config['carddav']['port']; ?></integer> - <key>CardDAVPrincipalURL</key> - <string>/SOGo/dav/<?php echo $email; ?></string> - <key>CardDAVUseSSL</key> - <true/> - <key>CardDAVUsername</key> - <string><?php echo $email; ?></string> - <key>PayloadDescription</key> - <string>Configures CardDAV accounts</string> - <key>PayloadDisplayName</key> - <string>CardDAV (<?php echo $domain; ?>)</string> - <key>PayloadIdentifier</key> - <string><?php echo $identifier; ?>.carddav</string> - <key>PayloadOrganization</key> - <string></string> - <key>PayloadType</key> - <string>com.apple.carddav.account</string> - <key>PayloadUUID</key> - <string>0797EF2B-B1F1-4BC7-ABCD-4580862252B4</string> - <key>PayloadVersion</key> - <integer>1</integer> - </dict> - </array> - <key>PayloadDescription</key> - <string>IMAP, CalDAV, CardDAV</string> - <key>PayloadDisplayName</key> - <string><?php echo $domain; ?> Mailcow</string> - <key>PayloadIdentifier</key> - <string><?php echo $identifier; ?></string> - <key>PayloadOrganization</key> - <string></string> - <key>PayloadRemovalDisallowed</key> - <false/> - <key>PayloadType</key> - <string>Configuration</string> - <key>PayloadUUID</key> - <string>5EE248C5-ACCB-42D8-9199-8F8ED08D5624</string> - <key>PayloadVersion</key> - <integer>1</integer> -</dict> + <dict> + <key>PayloadContent</key> + <array> + <dict> + <key>EmailAccountDescription</key> + <string><?=$email?></string> + <key>EmailAccountType</key> + <string>EmailTypeIMAP</string> + <key>EmailAccountName</key> + <string><?=$displayname?></string> + <key>EmailAddress</key> + <string><?=$email?></string> + <key>IncomingMailServerAuthentication</key> + <string>EmailAuthPassword</string> + <key>IncomingMailServerHostName</key> + <string><?=$autodiscover_config['imap']['server']?></string> + <key>IncomingMailServerPortNumber</key> + <integer><?=$autodiscover_config['imap']['port']?></integer> + <key>IncomingMailServerUseSSL</key> + <true/> + <key>IncomingMailServerUsername</key> + <string><?=$email?></string> + <key>OutgoingMailServerAuthentication</key> + <string>EmailAuthPassword</string> + <key>OutgoingMailServerHostName</key> + <string><?=$autodiscover_config['smtp']['server']?></string> + <key>OutgoingMailServerPortNumber</key> + <integer><?=$autodiscover_config['smtp']['port']?></integer> + <key>OutgoingMailServerUseSSL</key> + <true/> + <key>OutgoingMailServerUsername</key> + <string><?=$email?></string> + <key>OutgoingPasswordSameAsIncomingPassword</key> + <true/> + <key>PayloadDescription</key> + <string>Configures email account.</string> + <key>PayloadDisplayName</key> + <string>IMAP Account (<?=$email?>)</string> + <key>PayloadIdentifier</key> + <string><?=$identifier?>.email</string> + <key>PayloadOrganization</key> + <string></string> + <key>PayloadType</key> + <string>com.apple.mail.managed</string> + <key>PayloadUUID</key> + <string><?=getGUID()?></string> + <key>PayloadVersion</key> + <integer>1</integer> + <key>PreventAppSheet</key> + <false/> + <key>PreventMove</key> + <false/> + <key>SMIMEEnabled</key> + <false/> + </dict> + <?php if($onlyEmailAccount === false): ?> + <dict> + <key>CalDAVAccountDescription</key> + <string><?=$email?></string> + <key>CalDAVHostName</key> + <string><?=$autodiscover_config['caldav']['server']?></string> + <key>CalDAVPort</key> + <real><?=$autodiscover_config['caldav']['port']?></real> + <key>CalDAVPrincipalURL</key> + <string>/SOGo/dav/<?=$email?></string> + <key>CalDAVUseSSL</key> + <true/> + <key>CalDAVUsername</key> + <string><?=$email?></string> + <key>PayloadDescription</key> + <string>Configures CalDAV account.</string> + <key>PayloadDisplayName</key> + <string>CalDAV (<?=$email?>)</string> + <key>PayloadIdentifier</key> + <string><?=$identifier?>.CalDAV</string> + <key>PayloadOrganization</key> + <string></string> + <key>PayloadType</key> + <string>com.apple.caldav.account</string> + <key>PayloadUUID</key> + <string><?=getGUID()?></string> + <key>PayloadVersion</key> + <integer>1</integer> + </dict> + <dict> + <key>CardDAVAccountDescription</key> + <string><?=$email?></string> + <key>CardDAVHostName</key> + <string><?=$autodiscover_config['carddav']['server']?></string> + <key>CardDAVPort</key> + <integer><?=$autodiscover_config['carddav']['port']?></integer> + <key>CardDAVPrincipalURL</key> + <string>/SOGo/dav/<?=$email?></string> + <key>CardDAVUseSSL</key> + <true/> + <key>CardDAVUsername</key> + <string><?=$email?></string> + <key>PayloadDescription</key> + <string>Configures CardDAV accounts</string> + <key>PayloadDisplayName</key> + <string>CardDAV (<?=$email?>)</string> + <key>PayloadIdentifier</key> + <string><?=$identifier?>.carddav</string> + <key>PayloadOrganization</key> + <string></string> + <key>PayloadType</key> + <string>com.apple.carddav.account</string> + <key>PayloadUUID</key> + <string><?=getGUID()?></string> + <key>PayloadVersion</key> + <integer>1</integer> + </dict> + <?php endif; ?> + </array> + <key>PayloadDescription</key> + <string><?=$description?></string> + <key>PayloadDisplayName</key> + <string><?=$email?></string> + <key>PayloadIdentifier</key> + <string><?=$identifier?></string> + <key>PayloadOrganization</key> + <string><?=$UI_TEXTS['main_name']?></string> + <key>PayloadRemovalDisallowed</key> + <false/> + <key>PayloadType</key> + <string>Configuration</string> + <key>PayloadUUID</key> + <string><?=getGUID()?></string> + <key>PayloadVersion</key> + <integer>1</integer> + </dict> </plist> diff --git a/data/web/modals/admin.php b/data/web/modals/admin.php index 7e8656e5..2303519e 100644 --- a/data/web/modals/admin.php +++ b/data/web/modals/admin.php @@ -17,13 +17,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="desc"><?=$lang['admin']['rsetting_desc'];?>:</label> <div class="col-sm-10"> - <input type="text" class="form-control" name="desc" id="desc" required> + <input type="text" class="form-control" id="adminRspamdSettingsDesc" name="desc" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="content"><?=$lang['admin']['rsetting_content'];?>:</label> <div class="col-sm-10"> - <textarea class="form-control" id="content" name="content" rows="10"><?=$rsetting_details['content'];?></textarea> + <textarea class="form-control" id="adminRspamdSettingsContent" name="content" rows="10"><?=$rsetting_details['content'];?></textarea> </div> </div> <div class="form-group"> @@ -35,7 +35,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="rsetting" data-api-url='add/rsetting' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> <?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="rsetting" data-api-url='add/rsetting' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> <?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -60,14 +60,14 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="username"><?=$lang['admin']['username'];?>:</label> <div class="col-sm-10"> - <input type="text" class="form-control" name="username" id="username" required> + <input type="text" class="form-control" name="username" required> ↳ <kbd>a-z A-Z - _ .</kbd> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="name"><?=$lang['admin']['admin_domains'];?>:</label> <div class="col-sm-10"> - <select title="<?=$lang['admin']['search_domain_da'];?>" style="width:100%" name="domains" size="5" multiple> + <select title="<?=$lang['admin']['search_domain_da'];?>" class="full-width-select" name="domains" size="5" multiple> <?php foreach (mailbox('get', 'domains') as $domain) { echo "<option>".htmlspecialchars($domain)."</option>"; @@ -79,13 +79,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="password"><?=$lang['admin']['password'];?>:</label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password" id="password" placeholder="" required> + <input type="password" class="form-control" data-hibp="true" name="password" placeholder="" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="password2"><?=$lang['admin']['password_repeat'];?>:</label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password2" id="password2" placeholder="" required> + <input type="password" class="form-control" name="password2" placeholder="" required> </div> </div> <div class="form-group"> @@ -97,7 +97,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="add_domain_admin" data-api-url='add/domain-admin' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> <?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_domain_admin" data-api-url='add/domain-admin' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> <?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -105,35 +105,82 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> </div> </div><!-- add domain admin modal --> -<!-- test relayhost modal --> -<div class="modal fade" id="testRelayhostModal" tabindex="-1" role="dialog" aria-hidden="true"> +<!-- add admin modal --> +<div class="modal fade" id="addAdminModal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog modal-lg"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">ร</span></button> - <h3 class="modal-title"><span class="glyphicon glyphicon-stats"></span> Relayhost</h3> + <h3 class="modal-title"><?=$lang['admin']['add_admin'];?></h3> </div> <div class="modal-body"> - <form class="form-horizontal" data-cached-form="true" id="test_relayhost_form" role="form" method="post"> - <input type="hidden" class="form-control" name="relayhost_id" id="relayhost_id"> + <form class="form-horizontal" data-cached-form="true" data-id="add_admin" role="form" method="post"> <div class="form-group"> - <label class="control-label col-sm-2" for="mail_from"><?=$lang['admin']['relay_from'];?></label> + <label class="control-label col-sm-2" for="username"><?=$lang['admin']['username'];?>:</label> <div class="col-sm-10"> - <input type="text" class="form-control" name="mail_from" id="mail_from" placeholder="relay@example.org"> + <input type="text" class="form-control" name="username" required> + ↳ <kbd>a-z A-Z - _ .</kbd> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="password"><?=$lang['admin']['password'];?>:</label> + <div class="col-sm-10"> + <input type="password" class="form-control" data-hibp="true" name="password" placeholder="" required> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="password2"><?=$lang['admin']['password_repeat'];?>:</label> + <div class="col-sm-10"> + <input type="password" class="form-control" name="password2" placeholder="" required> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="test_relayhost" href="#"><?=$lang['admin']['relay_run'];?></button> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="active" checked> <?=$lang['admin']['active'];?></label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-default" data-action="add_item" data-id="add_admin" data-api-url='add/admin' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> <?=$lang['admin']['add'];?></button> + </div> + </div> + </form> + </div> + </div> + </div> +</div><!-- add admin modal --> +<!-- test transport modal --> +<div class="modal fade" id="testTransportModal" tabindex="-1" role="dialog" aria-hidden="true"> + <div class="modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">ร</span></button> + <h3 class="modal-title"><span class="glyphicon glyphicon-stats"></span> Transport</h3> + </div> + <div class="modal-body"> + <form class="form-horizontal" data-cached-form="true" id="test_transport_form" role="form" method="post"> + <input type="hidden" class="form-control" name="transport_id" id="transport_id"> + <input type="hidden" class="form-control" name="transport_type" id="transport_type"> + <div class="form-group"> + <label class="control-label col-sm-2" for="mail_from"><?=$lang['admin']['relay_from'];?></label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="mail_from" placeholder="relay@example.org"> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-default" id="test_transport" href="#"><?=$lang['admin']['relay_run'];?></button> </div> </div> </form> <hr> - <div id="test_relayhost_result" style="font-size:10pt">-</div> + <div id="test_transport_result" style="font-size:10pt">-</div> </div> </div> </div> -</div><!-- test relayhost modal --> +</div><!-- test transport modal --> <!-- priv key modal --> <div class="modal fade" id="showDKIMprivKey" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog"> diff --git a/data/web/modals/footer.php b/data/web/modals/footer.php index e861ab4e..b7ebaf08 100644 --- a/data/web/modals/footer.php +++ b/data/web/modals/footer.php @@ -8,19 +8,19 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm <div class="modal-body"> <form role="form" method="post"> <div class="form-group"> - <input type="text" class="form-control" name="key_id" id="key_id" placeholder="<?=$lang['tfa']['key_id'];?>" autocomplete="off" required> + <input type="text" class="form-control" name="key_id" placeholder="<?=$lang['tfa']['key_id'];?>" autocomplete="off" required> </div> <hr> <p class="help-block"><?=$lang['tfa']['api_register'];?></p> <div class="form-group"> - <input type="text" class="form-control" name="yubico_id" id="yubico_id" placeholder="Yubico API ID" autocomplete="off" required> + <input type="text" class="form-control" name="yubico_id" placeholder="Yubico API ID" autocomplete="off" required> </div> <div class="form-group"> - <input type="text" class="form-control" name="yubico_key" id="yubico_key" placeholder="Yubico API Key" autocomplete="off" required> + <input type="text" class="form-control" name="yubico_key" placeholder="Yubico API Key" autocomplete="off" required> </div> <hr> <div class="form-group"> - <input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required> + <input type="password" class="form-control" name="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required> </div> <div class="form-group"> <div class="input-group"> @@ -43,10 +43,10 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm <div class="modal-body"> <form role="form" method="post" id="u2f_reg_form"> <div class="form-group"> - <input type="text" class="form-control" name="key_id" id="key_id" placeholder="<?=$lang['tfa']['key_id'];?>" autocomplete="off" required> + <input type="text" class="form-control" name="key_id" placeholder="<?=$lang['tfa']['key_id'];?>" autocomplete="off" required> </div> <div class="form-group"> - <input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required> + <input type="password" class="form-control" name="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required> </div> <hr> <p id="u2f_status_reg"></p> @@ -67,28 +67,28 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm <div class="modal-body"> <form role="form" method="post"> <div class="form-group"> - <input type="text" class="form-control" name="key_id" id="key_id" placeholder="<?=$lang['tfa']['key_id_totp'];?>" autocomplete="off" required> + <input type="text" class="form-control" name="key_id" placeholder="<?=$lang['tfa']['key_id_totp'];?>" autocomplete="off" required> </div> <div class="form-group"> - <input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required> + <input type="password" class="form-control" name="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required> </div> <hr> <?php $totp_secret = $tfa->createSecret(); ?> - <input type="hidden" value="<?=$totp_secret;?>" name="totp_secret" id="totp_secret"/> + <input type="hidden" value="<?=$totp_secret;?>" name="totp_secret"> <input type="hidden" name="tfa_method" value="totp"> <ol> <li> <p><?=$lang['tfa']['scan_qr_code'];?></p> - <img src="<?=$tfa->getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $totp_secret);?>"> + <img id="tfa-qr-img" data-totp-secret="<?=$totp_secret;?>" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="> <p class="help-block"><?=$lang['tfa']['enter_qr_code'];?>:<br /> <code><?=$totp_secret;?></code> </p> </li> <li> <p><?=$lang['tfa']['confirm_totp_token'];?>:</p> - <p><input type="number" style="width:33%" class="form-control" name="totp_confirm_token" id="totp_confirm_token" autocomplete="off" required></p> + <p><input type="number" style="width:33%" class="form-control" name="totp_confirm_token" autocomplete="off" required></p> <p><button class="btn btn-default" type="submit" name="set_tfa"><?=$lang['tfa']['confirm'];?></button></p> </li> </ol> @@ -105,7 +105,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm <div class="modal-body"> <form role="form" method="post"> <div class="input-group"> - <input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required> + <input type="password" class="form-control" name="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required> <span class="input-group-btn"> <input type="hidden" name="tfa_method" value="none"> <button class="btn btn-danger" type="submit" name="set_tfa"><?=$lang['tfa']['delete_tfa'];?></button> @@ -135,7 +135,7 @@ if (isset($_SESSION['pending_tfa_method'])): <div class="form-group"> <div class="input-group"> <span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span> - <input type="text" name="token" id="token" class="form-control" autocomplete="off" placeholder="Touch Yubikey" aria-describedby="yubi-addon"> + <input type="text" name="token" class="form-control" autocomplete="off" placeholder="Touch Yubikey" aria-describedby="yubi-addon"> <input type="hidden" name="tfa_method" value="yubi_otp"> </div> </div> @@ -160,7 +160,7 @@ if (isset($_SESSION['pending_tfa_method'])): <div class="form-group"> <div class="input-group"> <span class="input-group-addon" id="tfa-addon"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span></span> - <input type="number" min="000000" max="999999" name="token" id="token" class="form-control" placeholder="123456" aria-describedby="tfa-addon"> + <input type="number" min="000000" max="999999" name="token" class="form-control" placeholder="123456" aria-describedby="tfa-addon"> <input type="hidden" name="tfa_method" value="totp"> </div> </div> diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index b4a51893..aeb88cca 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -17,13 +17,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="local_part"><?=$lang['add']['mailbox_username'];?></label> <div class="col-sm-10"> - <input type="text" pattern="[A-Za-z0-9\.!#$%&'*+/=?^_`{|}~-]+" autocorrect="off" autocapitalize="none" class="form-control" name="local_part" id="local_part" required> + <input type="text" pattern="[A-Za-z0-9\.!#$%&'*+/=?^_`{|}~-]+" autocorrect="off" autocapitalize="none" class="form-control" name="local_part" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="domain"><?=$lang['add']['domain'];?>:</label> <div class="col-sm-10"> - <select data-live-search="true" id="addSelectDomain" name="domain" id="domain" required> + <select class="full-width-select" data-live-search="true" id="addSelectDomain" name="domain" required> <?php foreach (mailbox('get', 'domains') as $domain) { echo "<option>".htmlspecialchars($domain)."</option>"; @@ -35,7 +35,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="name"><?=$lang['add']['full_name'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="name" id="name"> + <input type="text" class="form-control" name="name"> </div> </div> <div class="form-group"> @@ -43,21 +43,20 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <br /><span id="quotaBadge" class="badge">max. - MiB</span> </label> <div class="col-sm-10"> - <input type="text" class="form-control" name="quota" min="1" max="" id="addInputQuota" disabled value="<?=$lang['add']['select_domain'];?>" required> - <small class="help-block">min. 1</small> + <input type="text" class="form-control" name="quota" min="0" max="" id="addInputQuota" disabled value="<?=$lang['add']['select_domain'];?>" required> + <small class="help-block">0 = โ</small> </div> </div> <div class="form-group"> - <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label> + <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?> (<a href="#" class="generate_password"><?=$lang['add']['generate'];?></a>)</label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password" id="password" placeholder="" required> - (<a href="#" class="generate_password">Generate</a>) + <input type="password" data-hibp="true" class="form-control" name="password" placeholder="" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="password2"><?=$lang['add']['password_repeat'];?></label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password2" id="password2" placeholder="" required> + <input type="password" class="form-control" name="password2" placeholder="" required> </div> </div> <div class="form-group"> @@ -69,7 +68,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="add_mailbox" data-api-url='add/mailbox' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_mailbox" data-api-url='add/mailbox' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -90,37 +89,37 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="domain"><?=$lang['add']['domain'];?>:</label> <div class="col-sm-10"> - <input type="text" autocorrect="off" autocapitalize="none" class="form-control" name="domain" id="domain" required> + <input type="text" autocorrect="off" autocapitalize="none" class="form-control" name="domain" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="description" id="description" required> + <input type="text" class="form-control" name="description" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="aliases"><?=$lang['add']['max_aliases'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="aliases" id="aliases" value="400" required> + <input type="number" class="form-control" name="aliases" value="400" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="mailboxes"><?=$lang['add']['max_mailboxes'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="mailboxes" id="mailboxes" value="10" required> + <input type="number" class="form-control" name="mailboxes" value="10" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="maxquota"><?=$lang['add']['mailbox_quota_m'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="maxquota" id="maxquota" value="3072" required> + <input type="number" class="form-control" name="maxquota" value="3072" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="quota"><?=$lang['add']['domain_quota_m'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="quota" id="quota" value="10240" required> + <input type="number" class="form-control" name="quota" value="10240" required> </div> </div> <div class="form-group"> @@ -132,12 +131,12 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <hr> <div class="form-group"> - <label class="control-label col-sm-2" for="quota">Ratelimit</label> + <label class="control-label col-sm-2" for="rl_frame"><?=$lang['acl']['ratelimit'];?></label> <div class="col-sm-7"> - <input name="rl_value" id="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled"> + <input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled"> </div> <div class="col-sm-3"> - <select name="rl_frame" id="rl_frame" class="form-control"> + <select name="rl_frame" class="form-control"> <option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option> <option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option> <option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option> @@ -159,8 +158,8 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <hr> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="add_domain" data-api-url='add/domain' data-api-attr='{}' href="#"><?=$lang['add']['add_domain_only'];?></button> - <button class="btn btn-default" id="add_item" data-id="add_domain" data-api-url='add/domain' data-api-attr='{"restart_sogo":"1"}' href="#"><?=$lang['add']['add_domain_restart'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_domain" data-api-url='add/domain' data-api-attr='{}' href="#"><?=$lang['add']['add_domain_only'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_domain" data-api-url='add/domain' data-api-attr='{"restart_sogo":"1"}' href="#"><?=$lang['add']['add_domain_restart'];?></button> </div> </div> <p><span class="glyphicon glyphicon-exclamation-sign text-danger"></span> <?=$lang['add']['restart_sogo_hint'];?></p> @@ -182,13 +181,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="description" id="description" required> + <input type="text" class="form-control" name="description" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="domain"><?=$lang['add']['domain'];?>:</label> <div class="col-sm-10"> - <select data-live-search="true" name="domain" id="domain" title="<?=$lang['add']['select'];?>" required> + <select data-live-search="true" name="domain" title="<?=$lang['add']['select'];?>" required> <?php foreach (mailbox('get', 'domains') as $domain) { echo "<option>".htmlspecialchars($domain)."</option>"; @@ -200,7 +199,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="domain"><?=$lang['add']['kind'];?>:</label> <div class="col-sm-10"> - <select name="kind" id="kind" title="<?=$lang['add']['select'];?>" required> + <select name="kind" title="<?=$lang['add']['select'];?>" required> <option value="location">Location</option> <option value="group">Group</option> <option value="thing">Thing</option> @@ -231,7 +230,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="add_resource" data-api-url='add/resource' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_resource" data-api-url='add/resource' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -263,13 +262,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <textarea id="textarea_alias_goto" autocorrect="off" autocapitalize="none" class="form-control" rows="5" id="goto" name="goto" required></textarea> <p><?=$lang['add']['target_address_info'];?></p> <div class="checkbox"> - <label><input class="goto_checkbox" id="goto_null" type="checkbox" value="1" name="goto_null"> <?=$lang['add']['goto_null'];?></label> + <label><input class="goto_checkbox" type="checkbox" value="1" name="goto_null"> <?=$lang['add']['goto_null'];?></label> </div> <div class="checkbox"> - <label><input class="goto_checkbox" id="goto_spam" type="checkbox" value="1" name="goto_spam"> <?=$lang['add']['goto_spam'];?></label> + <label><input class="goto_checkbox" type="checkbox" value="1" name="goto_spam"> <?=$lang['add']['goto_spam'];?></label> </div> <div class="checkbox"> - <label><input class="goto_checkbox" id="goto_ham" type="checkbox" value="1" name="goto_ham"> <?=$lang['add']['goto_ham'];?></label> + <label><input class="goto_checkbox" type="checkbox" value="1" name="goto_ham"> <?=$lang['add']['goto_ham'];?></label> </div> </div> </div> @@ -282,7 +281,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="add_alias" data-api-url='add/alias' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_alias" data-api-url='add/alias' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -311,7 +310,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="target_domain"><?=$lang['add']['target_domain'];?></label> <div class="col-sm-10"> - <select data-live-search="true" name="target_domain" id="target_domain" title="<?=$lang['add']['select'];?>" required> + <select data-live-search="true" name="target_domain" title="<?=$lang['add']['select'];?>" required> <?php foreach (mailbox('get', 'domains') as $domain) { echo "<option>".htmlspecialchars($domain)."</option>"; @@ -327,9 +326,23 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> </div> </div> + <hr> + <div class="form-group"> + <label class="control-label col-sm-2" for="rl_frame"><?=$lang['acl']['ratelimit'];?></label> + <div class="col-sm-7"> + <input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled"> + </div> + <div class="col-sm-3"> + <select name="rl_frame" class="form-control"> + <option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option> + <option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option> + <option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option> + </select> + </div> + </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="add_alias_domain" data-api-url='add/alias-domain' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_alias_domain" data-api-url='add/alias-domain' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -351,7 +364,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?>:</label> <div class="col-sm-10"> - <select data-live-search="true" id="addSelectUsername" name="username" id="username" required> + <select data-live-search="true" name="username" required> <?php $domains = mailbox('get', 'domains'); if (!empty($domains)) { @@ -369,32 +382,32 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="host1"><?=$lang['add']['hostname'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="host1" id="host1" required> + <input type="text" class="form-control" name="host1" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="port1"><?=$lang['add']['port'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="143" required> + <input type="number" class="form-control" name="port1" min="1" max="65535" value="143" required> <small class="help-block">1-65535</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="user1"><?=$lang['add']['username'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="user1" id="user1" required> + <input type="text" class="form-control" name="user1" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="password1"><?=$lang['add']['password'];?></label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password1" id="password1" required> + <input type="password" class="form-control" name="password1" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="enc1"><?=$lang['add']['enc_method'];?></label> <div class="col-sm-10"> - <select name="enc1" id="enc1" title="<?=$lang['add']['select'];?>" required> + <select name="enc1" title="<?=$lang['add']['select'];?>" required> <option selected>TLS</option> <option>SSL</option> <option>PLAIN</option> @@ -411,47 +424,47 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="subfolder2"><?=$lang['edit']['subfolder2'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="subfolder2" id="subfolder2" value="External"> + <input type="text" class="form-control" name="subfolder2" value="External"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="0"> + <input type="number" class="form-control" name="maxage" min="0" max="32000" value="0"> <small class="help-block">0-32000</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="maxbytespersecond"><?=$lang['edit']['maxbytespersecond'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="0"> + <input type="number" class="form-control" name="maxbytespersecond" min="0" max="125000000" value="0"> <small class="help-block">0-125000000</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="timeout1"><?=$lang['edit']['timeout1'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="timeout1" id="timeout1" min="1" max="32000" value="600"> + <input type="number" class="form-control" name="timeout1" min="1" max="32000" value="600"> <small class="help-block">1-32000</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="timeout2"><?=$lang['edit']['timeout2'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="timeout2" id="timeout2" min="1" max="32000" value="600"> + <input type="number" class="form-control" name="timeout2" min="1" max="32000" value="600"> <small class="help-block">1-32000</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="exclude"><?=$lang['add']['exclude'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="exclude" id="exclude" value="(?i)spam|(?i)junk"> + <input type="text" class="form-control" name="exclude" value="(?i)spam|(?i)junk"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="custom_params"><?=$lang['add']['custom_params'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="custom_params" id="custom_params" placeholder="--delete2folders --otheroption"> + <input type="text" class="form-control" name="custom_params" placeholder="--delete2folders --otheroption"> </div> </div> <div class="form-group"> @@ -505,7 +518,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="add_syncjob" data-api-url='add/syncjob' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_syncjob" data-api-url='add/syncjob' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -526,7 +539,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?>:</label> <div class="col-sm-10"> - <select data-live-search="true" id="addSelectUsername" name="username" id="username" required> + <select data-live-search="true" name="username" required> <?php $domains = mailbox('get', 'domains'); if (!empty($domains)) { @@ -544,7 +557,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="filter_type"><?=$lang['add']['sieve_type'];?>:</label> <div class="col-sm-10"> - <select id="addFilterType" name="filter_type" id="filter_type" required> + <select id="addFilterType" name="filter_type" required> <option value="prefilter">Prefilter</option> <option value="postfilter">Postfilter</option> </select> @@ -553,13 +566,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="script_desc"><?=$lang['add']['sieve_desc'];?>:</label> <div class="col-sm-10"> - <input type="text" class="form-control" name="script_desc" id="script_desc" required maxlength="255"> + <input type="text" class="form-control" name="script_desc" required maxlength="255"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="script_data">Script:</label> <div class="col-sm-10"> - <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="script_data" name="script_data" required></textarea> + <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required></textarea> </div> </div> <div class="form-group"> @@ -573,7 +586,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <div class="col-sm-offset-2 col-sm-10" id="add_filter_btns"> <button class="btn btn-default" id="validate_sieve" href="#"><?=$lang['add']['validate'];?></button> - <button class="btn btn-success" id="add_item" data-id="add_filter" data-api-url='add/filter' data-api-attr='{}' href="#" disabled><?=$lang['admin']['add'];?></button> + <button class="btn btn-success" id="add_sieve_script" data-action="add_item" data-id="add_filter" data-api-url='add/filter' data-api-attr='{}' href="#" disabled><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -594,7 +607,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="local_dest"><?=$lang['mailbox']['bcc_local_dest'];?>:</label> <div class="col-sm-10"> - <select data-live-search="true" id="addSelectLocalDest" name="local_dest" id="local_dest" required> + <select data-live-search="true" name="local_dest" required> <?php $domains = mailbox('get', 'domains'); $alias_domains = mailbox('get', 'alias_domains'); @@ -623,7 +636,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="type"><?=$lang['mailbox']['bcc_map_type'];?>:</label> <div class="col-sm-10"> - <select id="addFBCCType" name="type" id="type" required> + <select name="type" required> <option value="sender"><?=$lang['mailbox']['bcc_sender_map'];?></option> <option value="rcpt"><?=$lang['mailbox']['bcc_rcpt_map'];?></option> </select> @@ -632,7 +645,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="bcc_dest"><?=$lang['mailbox']['bcc_destination'];?>:</label> <div class="col-sm-10"> - <input type="text" class="form-control" name="bcc_dest" id="bcc_dest"> + <input type="text" class="form-control" name="bcc_dest"> </div> </div> <div class="form-group"> @@ -644,7 +657,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="add_item" data-id="add_bcc" data-api-url='add/bcc' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + <button class="btn btn-success" data-action="add_item" data-id="add_bcc" data-api-url='add/bcc' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -665,14 +678,14 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="recipient_map_old"><?=$lang['mailbox']['recipient_map_old'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="recipient_map_old" id="recipient_map_old"> + <input type="text" class="form-control" name="recipient_map_old"> <small><?=$lang['mailbox']['recipient_map_old_info'];?></small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="recipient_map_new"><?=$lang['mailbox']['recipient_map_new'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="recipient_map_new" id="recipient_map_new"> + <input type="text" class="form-control" name="recipient_map_new"> <small><?=$lang['mailbox']['recipient_map_new_info'];?></small> </div> </div> @@ -685,7 +698,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-success" id="add_item" data-id="add_recipient_map" data-api-url='add/recipient_map' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + <button class="btn btn-success" data-action="add_item" data-id="add_recipient_map" data-api-url='add/recipient_map' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -693,6 +706,62 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> </div> </div><!-- add add_recipient_map modal --> +<!-- add add_tls_policy_map modal --> +<div class="modal fade" id="addTLSPolicyMapAdmin" tabindex="-1" role="dialog" aria-hidden="true"> + <div class="modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">ร</span></button> + <h3 class="modal-title"><?=$lang['mailbox']['tls_policy_maps'];?></h3> + </div> + <div class="modal-body"> + <form class="form-horizontal" data-cached-form="true" role="form" data-id="add_tls_policy_map"> + <div class="form-group"> + <label class="control-label col-sm-2" for="dest"><?=$lang['mailbox']['tls_map_dest'];?></label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="dest"> + <small><?=$lang['mailbox']['tls_map_dest_info'];?></small> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="policy"><?=$lang['mailbox']['tls_map_policy'];?>:</label> + <div class="col-sm-10"> + <select class="full-width-select" name="policy" required> + <option value="none">none</option> + <option value="may">may</option> + <option value="encrypt">encrypt</option> + <option value="dane">dane</option> + <option value="dane-only">dane-only</option> + <option value="fingerprint">fingerprint</option> + <option value="verify">verify</option> + <option value="secure">secure</option> + </select> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="parameters"><?=$lang['mailbox']['tls_map_parameters'];?></label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="parameters"> + <small><?=$lang['mailbox']['tls_map_parameters_info'];?></small> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="active" checked> <?=$lang['add']['active'];?></label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-success" data-action="add_item" data-id="add_tls_policy_map" data-api-url='add/tls-policy-map' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + </div> + </div> + </form> + </div> + </div> + </div> +</div><!-- add add_tls_policy_map modal --> <!-- log modal --> <div class="modal fade" id="syncjobLogModal" tabindex="-1" role="dialog" aria-labelledby="syncjobLogModalLabel"> <div class="modal-dialog modal-lg" role="document"> @@ -716,24 +785,3 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> </div> </div><!-- DNS info modal --> -<script> -$('#addResourceModal').on('shown.bs.modal', function() { - $("#multiple_bookings").val($("#multiple_bookings_select").val()); - if ($("#multiple_bookings").val() == "custom") { - $("#multiple_bookings_custom_div").show(); - $("#multiple_bookings").val($("#multiple_bookings_custom").val()); - } -}) -$("#multiple_bookings_select").change(function() { - $("#multiple_bookings").val($("#multiple_bookings_select").val()); - if ($("#multiple_bookings").val() == "custom") { - $("#multiple_bookings_custom_div").show(); - } - else { - $("#multiple_bookings_custom_div").hide(); - } -}); -$("#multiple_bookings_custom").bind ("change keypress keyup blur", function () { - $("#multiple_bookings").val($("#multiple_bookings_custom").val()); -}); -</script> diff --git a/data/web/modals/quarantine.php b/data/web/modals/quarantine.php index 41a63413..e1929927 100644 --- a/data/web/modals/quarantine.php +++ b/data/web/modals/quarantine.php @@ -25,10 +25,26 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <label for="qid_detail_text_from_html"><h4><?=$lang['quarantine']['text_from_html_content'];?>:</h4></label> <pre id="qid_detail_text_from_html"></pre> </div> + <?php + if ($_SESSION['acl']['quarantine_attachments'] == 1): + ?> <div class="form-group"> <label for="qid_detail_atts"><h4><?=$lang['quarantine']['atts'];?>:</h4></label> <div id="qid_detail_atts">-</div> </div> + <?php + endif; + ?> + <div class="btn-group dropup" data-acl="<?=$_SESSION['acl']['quarantine'];?>"> + <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['quarantine']['quick_actions'];?> <span class="caret"></span></a> + <ul class="dropdown-menu"> + <li><a data-action="edit_selected" data-id="qitems_single" data-item="" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#"><?=$lang['quarantine']['release'];?></a></li> + <li role="separator" class="divider"></li> + <li><a data-action="edit_selected" data-id="qitems_single" data-item="" data-api-url='edit/qitem' data-api-attr='{"action":"learnspam"}' href="#"><?=$lang['quarantine']['learn_spam_delete'];?></a></li> + <li role="separator" class="divider"></li> + <li><a data-action="delete_selected" data-id="qitems_single" data-item="" data-api-url='delete/qitem' href="#"><?=$lang['quarantine']['remove'];?></a></li> + </ul> + </div> </div> </div> </div> diff --git a/data/web/modals/user.php b/data/web/modals/user.php index 62ccf1b1..c45283d2 100644 --- a/data/web/modals/user.php +++ b/data/web/modals/user.php @@ -18,32 +18,32 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-2" for="host1"><?=$lang['add']['hostname'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="host1" id="host1" required> + <input type="text" class="form-control" name="host1" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="port1"><?=$lang['add']['port'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="143" required> + <input type="number" class="form-control" name="port1" min="1" max="65535" value="143" required> <small class="help-block">1-65535</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="user1"><?=$lang['add']['username'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="user1" id="user1" required> + <input type="text" class="form-control" name="user1" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="password1"><?=$lang['add']['password'];?></label> <div class="col-sm-10"> - <input type="password" class="form-control" name="password1" id="password1" required> + <input type="password" class="form-control" name="password1" data-hibp="true" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="enc1"><?=$lang['add']['enc_method'];?></label> <div class="col-sm-10"> - <select name="enc1" id="enc1" title="<?=$lang['add']['select'];?>" required> + <select name="enc1" title="<?=$lang['add']['select'];?>" required> <option selected>TLS</option> <option>SSL</option> <option>PLAIN</option> @@ -54,67 +54,94 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <label class="control-label col-sm-2" for="mins_interval"><?=$lang['add']['mins_interval'];?></label> <div class="col-sm-10"> <input type="number" class="form-control" name="mins_interval" min="1" max="3600" value="20" required> - <small class="help-block">10-3600</small> + <small class="help-block">1-3600</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="subfolder2"><?=$lang['edit']['subfolder2'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="subfolder2" id="subfolder2" value="External"> + <input type="text" class="form-control" name="subfolder2" value="External"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="0"> + <input type="number" class="form-control" name="maxage" min="0" max="32000" value="0"> <small class="help-block">0-32000</small> </div> </div> <div class="form-group"> <label class="control-label col-sm-2" for="maxbytespersecond"><?=$lang['edit']['maxbytespersecond'];?></label> <div class="col-sm-10"> - <input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="0"> + <input type="number" class="form-control" name="maxbytespersecond" min="0" max="125000000" value="0"> <small class="help-block">0-125000000</small> </div> </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="timeout1"><?=$lang['edit']['timeout1'];?></label> + <div class="col-sm-10"> + <input type="number" class="form-control" name="timeout1" min="1" max="32000" value="600"> + <small class="help-block">1-32000</small> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="timeout2"><?=$lang['edit']['timeout2'];?></label> + <div class="col-sm-10"> + <input type="number" class="form-control" name="timeout2" min="1" max="32000" value="600"> + <small class="help-block">1-32000</small> + </div> + </div> <div class="form-group"> <label class="control-label col-sm-2" for="exclude"><?=$lang['add']['exclude'];?></label> <div class="col-sm-10"> - <input type="text" class="form-control" name="exclude" id="exclude" value="(?i)spam|(?i)junk"> + <input type="text" class="form-control" name="exclude" value="(?i)spam|(?i)junk"> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-2" for="custom_params"><?=$lang['add']['custom_params'];?></label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="custom_params" placeholder="--delete2folders --otheroption"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label><input type="checkbox" value="1" name="delete2duplicates" checked> <?=$lang['add']['delete2duplicates'];?></label> + <label><input type="checkbox" value="1" name="delete2duplicates" checked> <?=$lang['add']['delete2duplicates'];?> (--delete2duplicates)</label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label><input type="checkbox" value="1" name="delete1"> <?=$lang['add']['delete1'];?></label> + <label><input type="checkbox" value="1" name="delete1"> <?=$lang['add']['delete1'];?> (--delete1)</label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label><input type="checkbox" value="1" name="delete2"> <?=$lang['add']['delete2'];?></label> + <label><input type="checkbox" value="1" name="delete2"> <?=$lang['add']['delete2'];?> (--delete2)</label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label><input type="checkbox" value="1" name="automap"> <?=$lang['add']['automap'];?></label> + <label><input type="checkbox" value="1" name="automap" checked> <?=$lang['add']['automap'];?> (--automap)</label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> - <label><input type="checkbox" value="1" name="skipcrossduplicates"> <?=$lang['add']['skipcrossduplicates'];?></label> + <label><input type="checkbox" value="1" name="skipcrossduplicates"> <?=$lang['add']['skipcrossduplicates'];?> (--skipcrossduplicates)</label> + </div> + </div> + </div> + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <div class="checkbox"> + <label><input type="checkbox" value="1" name="subscribeall" checked> <?=$lang['add']['subscribeall'];?> (--subscribeall)</label> </div> </div> </div> @@ -127,7 +154,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> - <button class="btn btn-default" id="add_item" data-id="add_syncjob" data-api-url='add/syncjob' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_syncjob" data-api-url='add/syncjob' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button> </div> </div> </form> @@ -141,7 +168,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="modal-content"> <div class="modal-header"><h4 class="modal-title">Log</h4></div> <div class="modal-body"> - <textarea class="form-control" rows="20" id="logText" spellcheck="false"></textarea> + <textarea class="form-control textarea-code" rows="20" id="logText" spellcheck="false"></textarea> </div> </div> </div> @@ -155,13 +182,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-3" for="user_new_pass"><?=$lang['user']['new_password'];?></label> <div class="col-sm-5"> - <input type="password" class="form-control" name="user_new_pass" id="user_new_pass" autocomplete="off" required> + <input type="password" data-hibp="true" class="form-control" name="user_new_pass" autocomplete="off" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3" for="user_new_pass2"><?=$lang['user']['new_password_repeat'];?></label> <div class="col-sm-5"> - <input type="password" class="form-control" name="user_new_pass2" id="user_new_pass2" autocomplete="off" required> + <input type="password" class="form-control" name="user_new_pass2" autocomplete="off" required> <p class="help-block"><?=$lang['user']['new_password_description'];?></p> </div> </div> @@ -169,12 +196,12 @@ if (!isset($_SESSION['mailcow_cc_role'])) { <div class="form-group"> <label class="control-label col-sm-3" for="user_old_pass"><?=$lang['user']['password_now'];?></label> <div class="col-sm-5"> - <input type="password" class="form-control" name="user_old_pass" id="user_old_pass" autocomplete="off" required> + <input type="password" class="form-control" name="user_old_pass" autocomplete="off" required> </div> </div> <div class="form-group"> <div class="col-sm-offset-3 col-sm-9"> - <button class="btn btn-default" id="edit_selected" data-id="pwchange" data-item="null" data-api-url='edit/self' data-api-attr='{}' href="#"><?=$lang['user']['change_password'];?></button> + <button class="btn btn-default" data-action="edit_selected" data-id="pwchange" data-item="null" data-api-url='edit/self' data-api-attr='{}' href="#"><?=$lang['user']['change_password'];?></button> </div> </div> </form> @@ -195,4 +222,4 @@ if (!isset($_SESSION['mailcow_cc_role'])) { </div> </div> </div> -</div><!-- sieve filter modal --> \ No newline at end of file +</div><!-- sieve filter modal --> diff --git a/data/web/qhandler.php b/data/web/qhandler.php new file mode 100644 index 00000000..fd4dc235 --- /dev/null +++ b/data/web/qhandler.php @@ -0,0 +1,68 @@ +<?php +session_start(); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; +if (preg_match("/^([a-f0-9]{64})$/", $_POST['quick_release']) || preg_match("/^([a-f0-9]{64})$/", $_POST['quick_delete'])) { +?> +<div class="container"> + <div class="row"> + <div class="col-md-offset-2 col-md-8"> + <div class="panel panel-default"> + <div class="panel-heading"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <?= $lang['header']['quarantine']; ?></div> + <div class="panel-body"> + <legend><?=(isset($_POST['quick_release'])) ? $lang['quarantine']['release'] : $lang['quarantine']['remove'];?></legend> + <p><?=$lang['quarantine']['qhandler_success'];?></p> + </div> + </div> + </div> + </div> <!-- /row --> +</div> <!-- /container --> +<?php +} +elseif (in_array($_GET['action'], array('release', 'delete'))) { + if (preg_match("/^([a-f0-9]{64})$/", $_GET['hash'])) { + if ($_GET['action'] == "release"): +?> +<div class="container"> + <div class="row"> + <div class="col-md-offset-2 col-md-8"> + <div class="panel panel-default"> + <div class="panel-heading"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <?= $lang['header']['quarantine']; ?></div> + <div class="panel-body"> + <legend><?=$lang['quarantine']['release'];?></legend> + <form method="post" autofill="off"> + <div class="form-group"> + <button type="submit" class="btn btn-success" name="quick_release" value="<?=$_GET['hash'];?>"><?= $lang['tfa']['confirm']; ?></button> + </div> + </form> + </div> + </div> + </div> + </div> <!-- /row --> +</div> <!-- /container --> +<?php + elseif ($_GET['action'] == "delete"): +?> +<div class="container"> + <div class="row"> + <div class="col-md-offset-2 col-md-8"> + <div class="panel panel-default"> + <div class="panel-heading"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <?= $lang['header']['quarantine']; ?></div> + <div class="panel-body"> + <legend><?=$lang['quarantine']['remove'];?></legend> + <form method="post" autofill="off"> + <div class="form-group"> + <button type="submit" class="btn btn-success" name="quick_delete" value="<?=$_GET['hash'];?>"><?= $lang['tfa']['confirm']; ?></button> + </div> + </form> + </div> + </div> + </div> + </div> <!-- /row --> +</div> <!-- /container --> +<?php + endif; + } +} +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; +?> diff --git a/data/web/quarantine.php b/data/web/quarantine.php index 87f565a1..60c323d9 100644 --- a/data/web/quarantine.php +++ b/data/web/quarantine.php @@ -1,8 +1,8 @@ <?php -require_once "inc/prerequisites.inc.php"; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; if (isset($_SESSION['mailcow_cc_role'])) { -require_once "inc/header.inc.php"; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; ?> @@ -14,22 +14,36 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; <h3 class="panel-title"><?=$lang['quarantine']['quarantine'];?></h3> </div> <p style="margin:10px" class="help-block"><?=$lang['quarantine']['qinfo'];?></p> + <p style="margin:10px"> + <?php + if (empty(quarantine('settings')['retention_size']) || empty(quarantine('settings')['max_size'])): + ?> + <div class="panel-body"><div class="alert alert-info"><?=$lang['quarantine']['disabled_by_config'];?></div></div> + <?php + endif; + ?> + </p> <div class="table-responsive"> <table id="quarantinetable" class="table table-striped"></table> </div> <div class="mass-actions-quarantine"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="qitems" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['quarantine']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['quarantine']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#"><?=$lang['quarantine']['release'];?></a></li> + <li><a data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#"><?=$lang['quarantine']['release'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"learnspam"}' href="#"><?=$lang['quarantine']['learn_spam_delete'];?></a></li> + <li><a data-action="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"learnspam"}' href="#"><?=$lang['quarantine']['learn_spam_delete'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="qitems" data-api-url='delete/qitem' href="#"><?=$lang['quarantine']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="qitems" data-api-url='delete/qitem' href="#"><?=$lang['quarantine']['remove'];?></a></li> </ul> </div> </div> + <hr> + <div class="panel-body help-block"> + <p><span class="dot-danger"></span> <?=$lang['quarantine']['high_danger'];?></p> + <p><span class="dot-neutral"></span> <?=$lang['quarantine']['neutral_danger'];?></p> + </div> </div> </div> <!-- /col-md-12 --> </div> <!-- /row --> @@ -40,6 +54,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/quarantine.php'; <script type='text/javascript'> <?php $lang_mailbox = json_encode($lang['quarantine']); +echo "var acl = '". json_encode($_SESSION['acl']) . "';\n"; echo "var lang = ". $lang_mailbox . ";\n"; echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n"; $role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin'; @@ -47,9 +62,8 @@ echo "var role = '". $role . "';\n"; echo "var pagination_size = '". $PAGINATION_SIZE . "';\n"; ?> </script> -<script src="js/footable.min.js"></script> -<script src="js/quarantine.js"></script> <?php +$js_minifier->add('/web/js/site/quarantine.js'); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; } else { header('Location: /'); diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php new file mode 100644 index 00000000..08fb1b0b --- /dev/null +++ b/data/web/sogo-auth.php @@ -0,0 +1,86 @@ +<?php + +$ALLOW_ADMIN_EMAIL_LOGIN = (preg_match( + "/^([yY][eE][sS]|[yY])+$/", + $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"] +)); + +$session_var_user_allowed = 'sogo-sso-user-allowed'; +$session_var_pass = 'sogo-sso-pass'; + +// prevent if feature is disabled +if (!$ALLOW_ADMIN_EMAIL_LOGIN) { + header('HTTP/1.0 403 Forbidden'); + echo "this feature is disabled"; + exit; +} +// validate credentials for basic auth requests +elseif (isset($_SERVER['PHP_AUTH_USER'])) { + // load prerequisites only when required + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; + $username = $_SERVER['PHP_AUTH_USER']; + $password = $_SERVER['PHP_AUTH_PW']; + $login_check = check_login($username, $password); + if ($login_check === 'user') { + header("X-User: $username"); + header("X-Auth: Basic ".base64_encode("$username:$password")); + header("X-Auth-Type: Basic"); + exit; + } else { + header('HTTP/1.0 401 Unauthorized'); + echo 'Invalid login'; + exit; + } +} +// check permissions and redirect for direct GET ?login=xy requests +elseif (isset($_GET['login'])) { + // load prerequisites only when required + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; + // check permissions + if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['acl']['login_as'] == "1") { + $login = html_entity_decode(rawurldecode($_GET["login"])); + if (filter_var($login, FILTER_VALIDATE_EMAIL)) { + if (!empty(mailbox('get', 'mailbox_details', $login))) { + // load master password + $sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass"); + // register username and password in session + $_SESSION[$session_var_user_allowed][] = $login; + $_SESSION[$session_var_pass] = $sogo_sso_pass; + // redirect to sogo (sogo will get the correct credentials via nginx auth_request + header("Location: /SOGo/so/${login}"); + exit; + } + } + } + header('HTTP/1.0 403 Forbidden'); + exit; +} +// only check for admin-login on sogo GUI requests +elseif ( + strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0 +) { + // this is an nginx auth_request call, we check for existing sogo-sso session variables + session_start(); + // extract email address from "/SOGo/so/user@domain/xy" + $url_parts = explode("/", $_SERVER['HTTP_X_ORIGINAL_URI']); + $email = $url_parts[3]; + // check if this email is in session allowed list + if ( + !empty($email) && + filter_var($email, FILTER_VALIDATE_EMAIL) && + is_array($_SESSION[$session_var_user_allowed]) && + in_array($email, $_SESSION[$session_var_user_allowed]) + ) { + $username = $email; + $password = $_SESSION[$session_var_pass]; + header("X-User: $username"); + header("X-Auth: Basic ".base64_encode("$username:$password")); + header("X-Auth-Type: Basic"); + exit; + } +} + +// if username is empty, SOGo will use the normal login methods / login form +header("X-User: "); +header("X-Auth: "); +header("X-Auth-Type: "); diff --git a/data/web/user.php b/data/web/user.php index 07e28616..7fe37310 100644 --- a/data/web/user.php +++ b/data/web/user.php @@ -1,15 +1,16 @@ <?php -require_once("inc/prerequisites.inc.php"); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') { /* / DOMAIN ADMIN */ - require_once("inc/header.inc.php"); + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $tfa_data = get_tfa(); $username = $_SESSION['mailcow_cc_username']; + ?> <div class="container"> <h3><?=$lang['user']['user_settings'];?></h3> @@ -28,6 +29,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma else: echo "Last login: -"; endif; ?> </small></p> + <p> </div> </div> <hr> @@ -61,6 +63,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma </div> </div> </div> +</div> <?php } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { @@ -69,12 +72,12 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' / USER */ - require_once("inc/header.inc.php"); + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $username = $_SESSION['mailcow_cc_username']; $mailboxdata = mailbox('get', 'mailbox_details', $username); - $clientconfigstr = "host=" . urlencode($mailcow_hostname) . "&email=" . urlencode($username) . "&name=" . urlencode($mailboxdata['name']) . "&port=" . urlencode($autodiscover_config['caldav']['port']); + $clientconfigstr = "host=" . urlencode($mailcow_hostname) . "&email=" . urlencode($username) . "&name=" . urlencode($mailboxdata['name']) . "&ui=" . urlencode($_SERVER['HTTP_HOST']) . "&port=" . urlencode($autodiscover_config['caldav']['port']); if ($autodiscover_config['useEASforOutlook'] == 'yes') $clientconfigstr .= "&outlookEAS=1"; if (file_exists('thunderbird-plugins/version.csv')) { @@ -88,184 +91,229 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' fclose($fh); } } - ?> <div class="container"> -<h3><?=$lang['user']['user_settings'];?></h3> -<div class="panel panel-default"> -<div class="panel-heading"><?=$lang['user']['mailbox_details'];?></div> -<div class="panel-body"> - <div class="row"> - <div class="col-sm-offset-3 col-sm-9"> - <?php if ($mailboxdata['attributes']['force_pw_update'] == "1"): ?> - <div class="alert alert-danger"><?=$lang['user']['force_pw_update'];?></div> - <?php endif; ?> - <p><a href="#pwChangeModal" data-toggle="modal">[<?=$lang['user']['change_password'];?>]</a></p> - <p><a target="_blank" href="https://mailcow.github.io/mailcow-dockerized-docs/client/#<?=$clientconfigstr;?>">[<?=$lang['user']['client_configuration'];?>]</a></p> - <p><small> - <?php - if ($_SESSION['mailcow_cc_last_login']['remote']): - ?> - <span style="margin-right:10px" class="glyphicon glyphicon-log-in"></span> <span data-time="<?=$_SESSION['mailcow_cc_last_login']['time'];?>" class="last_login_date"></span> (<?=$_SESSION['mailcow_cc_last_login']['remote'];?>) - <?php - else: echo "Last login: -"; endif; - ?> - </small></p> - </div> - </div> + <!-- Nav tabs --> + <ul class="nav nav-tabs" role="tablist"> + <li role="presentation" class="active"><a href="#userSettings" aria-controls="userSettings" role="tab" data-toggle="tab"><?=$lang['user']['mailbox_details'];?></a></li> + <li role="presentation"><a href="#SpamAliases" aria-controls="SpamAliases" role="tab" data-toggle="tab"><?=$lang['user']['spam_aliases'];?></a></li> + <li role="presentation"><a href="#Spamfilter" aria-controls="Spamfilter" role="tab" data-toggle="tab"><?=$lang['user']['spamfilter'];?></a></li> + <li role="presentation"><a href="#Syncjobs" aria-controls="Syncjobs" role="tab" data-toggle="tab"><?=$lang['user']['sync_jobs'];?></a></li> + </ul> <hr> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"> <span class="glyphicon glyphicon-filter"></span></div> - <div class="col-md-9 col-xs-7"> - <p><a href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p> - </div> - </div> - <hr> - <?php // Get user information about aliases - $user_get_alias_details = user_get_alias_details($username); - ?> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>: - <p class="small"><?=$lang['user']['direct_aliases_desc'];?></p> - </div> - <div class="col-md-9 col-xs-7"> - <p><?=$user_get_alias_details['direct_aliases'];?></p> - </div> - </div> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>: - <p class="small"><?=$lang['user']['shared_aliases_desc'];?></p> - </div> - <div class="col-md-9 col-xs-7"> - <p><?=$user_get_alias_details['shared_aliases'];?></p> - </div> - </div> - <hr> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div> - <div class="col-md-9 col-xs-7"> - <p><?=$user_get_alias_details['aliases_also_send_as'];?></p> - </div> - </div> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_send_as_all'];?>:</div> - <div class="col-md-9 col-xs-7"> - <p><?=$user_get_alias_details['aliases_send_as_all'];?></p> - </div> - </div> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['is_catch_all'];?>:</div> - <div class="col-md-9 col-xs-7"> - <p><?=$user_get_alias_details['is_catch_all'];?></p> - </div> - </div> - <hr> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['in_use'];?>:</div> - <div class="col-md-5 col-xs-7"> - <div class="progress"> - <div class="progress-bar progress-bar-<?=$mailboxdata['percent_class'];?>" role="progressbar" aria-valuenow="<?=$mailboxdata['percent_in_use'];?>" aria-valuemin="0" aria-valuemax="100" style="min-width:2em;width: <?=$mailboxdata['percent_in_use'];?>%;"> - <?=$mailboxdata['percent_in_use'];?>% + + <div class="tab-content"> + + <div role="tabpanel" class="tab-pane active" id="userSettings"> + <div class="panel panel-default"> + <div class="panel-heading"><?=$lang['user']['mailbox_details'];?></div> + <div class="panel-body"> + <div class="row"> + <div class="col-sm-offset-3 col-sm-9"> + <?php if ($mailboxdata['attributes']['force_pw_update'] == "1"): ?> + <div class="alert alert-danger"><?=$lang['user']['force_pw_update'];?></div> + <?php endif; ?> + <p><a href="#pwChangeModal" data-toggle="modal">[<?=$lang['user']['change_password'];?>]</a></p> + <p><a target="_blank" href="https://mailcow.github.io/mailcow-dockerized-docs/client/#<?=$clientconfigstr;?>">[<?=$lang['user']['client_configuration'];?>]</a></p> + <p><a href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p> + <p><small> + <?php + if ($_SESSION['mailcow_cc_last_login']['remote']): + ?> + <span style="margin-right:10px" class="glyphicon glyphicon-log-in"></span> <span data-time="<?=$_SESSION['mailcow_cc_last_login']['time'];?>" class="last_login_date"></span> (<?=$_SESSION['mailcow_cc_last_login']['remote'];?>) + <?php + else: echo "Last login: -"; endif; + ?> + </small></p> + </div> + </div> + <hr> + <?php // Get user information about aliases + $user_get_alias_details = user_get_alias_details($username); + ?> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>: + <p class="small"><?=$lang['user']['direct_aliases_desc'];?></p> + </div> + <div class="col-md-9 col-xs-7"> + <?php + if ($user_get_alias_details['direct_aliases'] === false) { + echo '✘'; + } + else { + foreach (array_filter($user_get_alias_details['direct_aliases']) as $direct_alias => $direct_alias_meta) { + (!empty($direct_alias_meta['public_comment'])) ? + printf('%s — <span class="bg-info">%s</span><br>', $direct_alias, $direct_alias_meta['public_comment']) : + printf('%s<br>', $direct_alias); + } + } + ?> + </div> + </div> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>: + <p class="small"><?=$lang['user']['shared_aliases_desc'];?></p> + </div> + <div class="col-md-9 col-xs-7"> + <?php + if ($user_get_alias_details['shared_aliases'] === false) { + echo '✘'; + } + else { + foreach (array_filter($user_get_alias_details['shared_aliases']) as $shared_alias => $shared_alias_meta) { + (!empty($shared_alias_meta['public_comment'])) ? + printf('%s — <span class="bg-info">%s</span><br>', $shared_alias, $shared_alias_meta['public_comment']) : + + printf('%s<br>', $shared_alias); + } + } + ?> + </div> + </div> + <hr> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div> + <div class="col-md-9 col-xs-7"> + <p><?=($user_get_alias_details['aliases_also_send_as'] == '*') ? $lang['user']['sender_acl_disabled'] : $user_get_alias_details['aliases_also_send_as'];?></p> + </div> + </div> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_send_as_all'];?>:</div> + <div class="col-md-9 col-xs-7"> + <p><?=$user_get_alias_details['aliases_send_as_all'];?></p> + </div> + </div> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['is_catch_all'];?>:</div> + <div class="col-md-9 col-xs-7"> + <p><?=$user_get_alias_details['is_catch_all'];?></p> + </div> + </div> + <hr> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['in_use'];?>:</div> + <div class="col-md-5 col-xs-7"> + <div class="progress"> + <div class="progress-bar progress-bar-<?=$mailboxdata['percent_class'];?>" role="progressbar" aria-valuenow="<?=$mailboxdata['percent_in_use'];?>" aria-valuemin="0" aria-valuemax="100" style="min-width:2em;width: <?=$mailboxdata['percent_in_use'];?>%;"> + <?=$mailboxdata['percent_in_use'];?>% + </div> + </div> + <p><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=($mailboxdata['quota'] == 0) ? 'โ' : formatBytes($mailboxdata['quota'], 2);?><br><?=$mailboxdata['messages'];?> <?=$lang['user']['messages'];?></p> + </div> + </div> + <hr> + <?php + // Show tagging options + $get_tagging_options = mailbox('get', 'delimiter_action', $username); + ?> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tag_handling'];?>:</div> + <div class="col-md-9 col-xs-7"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['delimiter_action'];?>"> + <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subfolder") ? 'active' : null; ?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="delimiter_action" + data-api-url='edit/delimiter_action' + data-api-attr='{"tagged_mail_handler":"subfolder"}'><?=$lang['user']['tag_in_subfolder'];?></button> + <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subject") ? 'active' : null; ?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="delimiter_action" + data-api-url='edit/delimiter_action' + data-api-attr='{"tagged_mail_handler":"subject"}'><?=$lang['user']['tag_in_subject'];?></button> + <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "none") ? 'active' : null; ?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="delimiter_action" + data-api-url='edit/delimiter_action' + data-api-attr='{"tagged_mail_handler":"none"}'><?=$lang['user']['tag_in_none'];?></button> + </div> + <p class="help-block"><?=$lang['user']['tag_help_explain'];?></p> + <p class="help-block"><?=$lang['user']['tag_help_example'];?></p> + </div> + </div> + <?php + // Show TLS policy options + $get_tls_policy = mailbox('get', 'tls_policy', $username); + ?> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tls_policy'];?>:</div> + <div class="col-md-9 col-xs-7"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['tls_policy'];?>"> + <button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_in'] == "1") ? "active" : null;?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="tls_policy" + data-api-url='edit/tls_policy' + data-api-attr='{"tls_enforce_in":<?=($get_tls_policy['tls_enforce_in'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_in'];?></button> + <button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_out'] == "1") ? "active" : null;?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="tls_policy" + data-api-url='edit/tls_policy' + data-api-attr='{"tls_enforce_out":<?=($get_tls_policy['tls_enforce_out'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_out'];?></button> + </div> + <p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p> + </div> + </div> + <?php + // Show quarantine_notification options + $quarantine_notification = mailbox('get', 'quarantine_notification', $username); + ?> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['quarantine_notification'];?>:</div> + <div class="col-md-9 col-xs-7"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_notification'];?>"> + <button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "never") ? "active" : null;?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="quarantine_notification" + data-api-url='edit/quarantine_notification' + data-api-attr='{"quarantine_notification":"never"}'><?=$lang['user']['never'];?></button> + <button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "hourly") ? "active" : null;?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="quarantine_notification" + data-api-url='edit/quarantine_notification' + data-api-attr='{"quarantine_notification":"hourly"}'><?=$lang['user']['hourly'];?></button> + <button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "daily") ? "active" : null;?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="quarantine_notification" + data-api-url='edit/quarantine_notification' + data-api-attr='{"quarantine_notification":"daily"}'><?=$lang['user']['daily'];?></button> + <button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "weekly") ? "active" : null;?>" + data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="quarantine_notification" + data-api-url='edit/quarantine_notification' + data-api-attr='{"quarantine_notification":"weekly"}'><?=$lang['user']['weekly'];?></button> + </div> + <p class="help-block"><?=$lang['user']['quarantine_notification_info'];?></p> + </div> + </div> + <hr> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['eas_reset'];?>:</div> + <div class="col-md-9 col-xs-7"> + <button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['eas_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="eas_cache" data-api-url='delete/eas_cache' href="#"><?=$lang['user']['eas_reset_now'];?></button> + <p class="help-block"><?=$lang['user']['eas_reset_help'];?></p> + </div> + </div> + <div class="row"> + <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['sogo_profile_reset'];?>:</div> + <div class="col-md-9 col-xs-7"> + <button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['sogo_profile_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['sogo_profile_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="sogo_profile" data-api-url='delete/sogo_profile' href="#"><?=$lang['user']['sogo_profile_reset_now'];?></button> + <p class="help-block"><?=$lang['user']['sogo_profile_reset_help'];?></p> + </div> </div> </div> - <p><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=formatBytes($mailboxdata['quota'], 2);?>, <?=$mailboxdata['messages'];?> <?=$lang['user']['messages'];?></p> </div> </div> - <?php - ($_SESSION['acl']['delimiter_action'] == 0 && $_SESSION['acl']['delimiter_action'] == 0 && $_SESSION['acl']['delimiter_action'] == 0) ? null : '<hr>'; - // Show tagging options - if ($_SESSION['acl']['delimiter_action'] == 1): - $get_tagging_options = mailbox('get', 'delimiter_action', $username); - ?> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tag_handling'];?>:</div> - <div class="col-md-9 col-xs-7"> - <div class="btn-group"> - <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subfolder") ? 'active' : null; ?>" - id="edit_selected" - data-item="<?= htmlentities($username); ?>" - data-id="delimiter_action" - data-api-url='edit/delimiter_action' - data-api-attr='{"tagged_mail_handler":"subfolder"}'><?=$lang['user']['tag_in_subfolder'];?></button> - - <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subject") ? 'active' : null; ?>" - id="edit_selected" - data-item="<?= htmlentities($username); ?>" - data-id="delimiter_action" - data-api-url='edit/delimiter_action' - data-api-attr='{"tagged_mail_handler":"subject"}'><?=$lang['user']['tag_in_subject'];?></button> - - <button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "none") ? 'active' : null; ?>" - id="edit_selected" - data-item="<?= htmlentities($username); ?>" - data-id="delimiter_action" - data-api-url='edit/delimiter_action' - data-api-attr='{"tagged_mail_handler":"none"}'><?=$lang['user']['tag_in_none'];?></button> - - </div> - <p class="help-block"><?=$lang['user']['tag_help_explain'];?></p> - <p class="help-block"><?=$lang['user']['tag_help_example'];?></p> - </div> - </div> - <?php - endif; - // Show TLS policy options - if ($_SESSION['acl']['tls_policy'] == 1): - $get_tls_policy = mailbox('get', 'tls_policy', $username); - ?> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tls_policy'];?>:</div> - <div class="col-md-9 col-xs-7"> - <div class="btn-group"> - - <button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_in'] == "1") ? "active" : null;?>" - id="edit_selected" - data-item="<?= htmlentities($username); ?>" - data-id="tls_policy" - data-api-url='edit/tls_policy' - data-api-attr='{"tls_enforce_in":<?=($get_tls_policy['tls_enforce_in'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_in'];?></button> - - <button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_out'] == "1") ? "active" : null;?>" - id="edit_selected" - data-item="<?= htmlentities($username); ?>" - data-id="tls_policy" - data-api-url='edit/tls_policy' - data-api-attr='{"tls_enforce_out":<?=($get_tls_policy['tls_enforce_out'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_out'];?></button> - - </div> - <p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p> - </div> - </div> - <?php - endif; - // Rest EAS devices - if ($_SESSION['acl']['eas_reset'] == 1): - ?> - <div class="row"> - <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['eas_reset'];?>:</div> - <div class="col-md-9 col-xs-7"> - <button class="btn btn-xs btn-default" id="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="eas_cache" data-api-url='delete/eas_cache' href="#"><?=$lang['user']['eas_reset_now'];?></button> - <p class="help-block"><?=$lang['user']['eas_reset_help'];?></p> - </div> - </div> - <?php - endif; - ?> -</div> -</div> - -<!-- Nav tabs --> -<ul class="nav nav-pills nav-justified" role="tablist"> - <li role="presentation" class="active"><a href="#SpamAliases" aria-controls="SpamAliases" role="tab" data-toggle="tab"><?=$lang['user']['spam_aliases'];?></a></li> - <li role="presentation"><a href="#Spamfilter" aria-controls="Spamfilter" role="tab" data-toggle="tab"><?=$lang['user']['spamfilter'];?></a></li> - <li role="presentation"><a href="#Syncjobs" aria-controls="Syncjobs" role="tab" data-toggle="tab"><?=$lang['user']['sync_jobs'];?></a></li> -</ul> -<hr> - -<div class="tab-content"> - <div role="tabpanel" class="tab-pane active" id="SpamAliases"> + <div role="tabpanel" class="tab-pane" id="SpamAliases"> <div class="row"> <div class="col-md-12 col-sm-12 col-xs-12"> <div class="table-responsive"> @@ -273,35 +321,33 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' </div> </div> </div> - <?php - if ($_SESSION['acl']['spam_alias'] == 1): - ?> <div class="mass-actions-user"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_alias'];?>"> <div class="btn-group"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="tla" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-time"></span> + 1h</a></li> + <li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"1"}' href="#"><?=$lang['user']['expire_in'];?> 1 <?=$lang['user']['hour'];?></a></li> + <li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"6"}' href="#"><?=$lang['user']['expire_in'];?> 6 <?=$lang['user']['hours'];?></a></li> + <li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"24"}' href="#"><?=$lang['user']['expire_in'];?> 1 <?=$lang['user']['day'];?></a></li> + <li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"168"}' href="#"><?=$lang['user']['expire_in'];?> 1 <?=$lang['user']['week'];?></a></li> + <li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"672"}' href="#"><?=$lang['user']['expire_in'];?> 4 <?=$lang['user']['weeks'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-id="tla" data-api-url='delete/time_limited_alias' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="tla" data-api-url='delete/time_limited_alias' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> </div> <div class="btn-group"> <a class="btn btn-sm btn-success dropdown-toggle" data-toggle="dropdown" href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['alias_create_random'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"1"}' href="#">1 <?=$lang['user']['hour'];?></a></li> - <li><a id="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"6"}' href="#">6 <?=$lang['user']['hours'];?></a></li> - <li><a id="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"24"}' href="#">1 <?=$lang['user']['day'];?></a></li> - <li><a id="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"168"}' href="#">1 <?=$lang['user']['week'];?></a></li> - <li><a id="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"672"}' href="#">4 <?=$lang['user']['weeks'];?></a></li> + <li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"1"}' href="#">1 <?=$lang['user']['hour'];?></a></li> + <li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"6"}' href="#">6 <?=$lang['user']['hours'];?></a></li> + <li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"24"}' href="#">1 <?=$lang['user']['day'];?></a></li> + <li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"168"}' href="#">1 <?=$lang['user']['week'];?></a></li> + <li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"672"}' href="#">4 <?=$lang['user']['weeks'];?></a></li> </ul> </div> </div> </div> - <?php - endif; - ?> </div> <div role="tabpanel" class="tab-pane" id="Spamfilter"> @@ -309,7 +355,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' <form class="form-horizontal" role="form" data-id="spam_score" method="post"> <div class="form-group"> <div class="col-lg-6 col-sm-12"> - <input name="spam_score" id="spam_score" type="text" style="width: 100%;" + <input data-acl="<?=$_SESSION['acl']['spam_score'];?>" name="spam_score" id="spam_score" type="text" style="width: 100%;" data-provide="slider" data-slider-min="1" data-slider-max="2000" @@ -326,25 +372,25 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' <li><?=$lang['user']['spamfilter_yellow'];?></li> <li><?=$lang['user']['spamfilter_red'];?></li> </ul> - <p><i><?=$lang['user']['spamfilter_default_score'];?> 5:15</i></p> <p><?=$lang['user']['spamfilter_hint'];?></p> </div> </div> - <?php - if ($_SESSION['acl']['spam_score'] == 1): - ?> <div class="form-group"> <div class="col-sm-10"> - <button type="button" class="btn btn-sm btn-success" id="edit_selected" - data-item="<?= htmlentities($username); ?>" - data-id="spam_score" - data-api-url='edit/spam-score' - data-api-attr='{}'><?=$lang['user']['save_changes'];?></button> </div> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> + <a data-acl="<?=$_SESSION['acl']['spam_score'];?>" type="button" class="btn btn-sm btn-success" data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="spam_score" + data-api-url='edit/spam-score' + data-api-attr='{}'><?=$lang['user']['save_changes'];?></a> + <a data-acl="<?=$_SESSION['acl']['spam_score'];?>" type="button" class="btn btn-sm btn-default" data-action="edit_selected" + data-item="<?= htmlentities($username); ?>" + data-id="spam_score_reset" + data-api-url='edit/spam-score' + data-api-attr='{"spam_score":"default"}'><?=$lang['user']['spam_score_reset'];?></a> + </div> </div> - <?php - endif; - ?> </form> <hr> <div class="row"> @@ -354,26 +400,21 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' <div class="table-responsive"> <table class="table table-striped table-condensed" id="wl_policy_mailbox_table"></table> </div> - <?php - if ($_SESSION['acl']['spam_policy'] == 1): - ?> + <div class="mass-actions-user"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> - <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <a class="btn btn-sm btn-danger" data-action="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> </div> </div> <form class="form-inline" data-id="add_wl_policy_mailbox"> - <div class="input-group"> - <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required> + <div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> + <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required> <span class="input-group-btn"> - <button class="btn btn-default" id="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"wl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"wl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button> </span> </div> </form> - <?php - endif; - ?> </div> <div class="col-sm-6"> <h4><?=$lang['user']['spamfilter_bl'];?></h4> @@ -381,28 +422,22 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' <div class="table-responsive"> <table class="table table-striped table-condensed" id="bl_policy_mailbox_table"></table> </div> - <?php - if ($_SESSION['acl']['spam_policy'] == 1): - ?> + <div class="mass-actions-user"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> - <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <a class="btn btn-sm btn-danger" data-action="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> </div> </div> <form class="form-inline" data-id="add_bl_policy_mailbox"> - <div class="input-group"> - <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required> - <input type="hidden" name="username" value="<?= htmlentities($username) ;?>"> - <input type="hidden" name="object_list" value="bl"> + <div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> + <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required> <span class="input-group-btn"> - <button class="btn btn-default" id="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"bl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button> + <button class="btn btn-default" data-action="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"bl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button> </span> </div> </form> - <?php - endif; - ?> + </div> </div> </div> @@ -411,33 +446,27 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' <div class="table-responsive"> <table class="table table-striped" id="sync_job_table"></table> </div> - <?php - if ($_SESSION['acl']['syncjobs'] == 1): - ?> <div class="mass-actions-user"> - <div class="btn-group"> + <div class="btn-group" data-acl="<?=$_SESSION['acl']['syncjobs'];?>"> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a> <ul class="dropdown-menu"> - <li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> - <li><a id="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> + <li><a data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li> + <li><a data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li> <li role="separator" class="divider"></li> - <li><a id="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-id="syncjob" data-api-url='delete/syncjob' href="#"><?=$lang['mailbox']['remove'];?></a></li> + <li><a data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#"><?=$lang['mailbox']['remove'];?></a></li> </ul> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a> </div> </div> - <?php - endif; - ?> </div> </div> - + </div><!-- /container --> <div style="margin-bottom:200px;"></div> <?php } -if (isset($_SESSION['mailcow_cc_role'])) { +if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] != 'admin') { require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/user.php'; ?> <script type='text/javascript'> @@ -450,10 +479,9 @@ echo "var mailcow_cc_username = '". $_SESSION['mailcow_cc_username'] . "';\n"; echo "var pagination_size = '". $PAGINATION_SIZE . "';\n"; ?> </script> -<script src="js/footable.min.js"></script> -<script src="js/user.js"></script> <?php -require_once("inc/footer.inc.php"); +$js_minifier->add('/web/js/site/user.js'); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; } else { header('Location: /'); diff --git a/docker-compose.yml b/docker-compose.yml index 871961a8..cbef6a40 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '2.1' services: unbound-mailcow: - image: mailcow/unbound:1.1 + image: mailcow/unbound:1.6 build: ./data/Dockerfiles/unbound command: /usr/sbin/unbound environment: @@ -10,6 +10,7 @@ services: volumes: - ./data/conf/unbound/unbound.conf:/etc/unbound/unbound.conf:ro restart: always + tty: true networks: mailcow-network: ipv4_address: ${IPV4_NETWORK:-172.22.1}.254 @@ -20,6 +21,7 @@ services: image: mariadb:10.2 volumes: - mysql-vol-1:/var/lib/mysql/ + - mysql-socket-vol-1:/var/run/mysqld/ - ./data/conf/mysql/:/etc/mysql/conf.d/:ro environment: - TZ=${TZ} @@ -38,7 +40,7 @@ services: - mysql redis-mailcow: - image: redis:4-alpine + image: redis:5-alpine volumes: - redis-vol-1:/data/ restart: always @@ -53,16 +55,14 @@ services: - redis clamd-mailcow: - image: mailcow/clamd:1.13 + image: mailcow/clamd:1.22 build: ./data/Dockerfiles/clamd restart: always - tty: true environment: - TZ=${TZ} - SKIP_CLAMD=${SKIP_CLAMD:-n} volumes: - ./data/conf/clamav/:/etc/clamav/ - - ./data/conf/clamav/whitelist.ign2:/var/lib/clamav/whitelist.ign2 dns: - ${IPV4_NETWORK:-172.22.1}.254 networks: @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.24 + image: mailcow/rspamd:1.38 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: @@ -79,11 +79,10 @@ services: environment: - TZ=${TZ} volumes: - - ./data/conf/rspamd/custom/:/etc/rspamd/custom:ro - - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:rw - - ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:ro + - ./data/conf/rspamd/custom/:/etc/rspamd/custom + - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d + - ./data/conf/rspamd/local.d/:/etc/rspamd/local.d - ./data/conf/rspamd/lua/:/etc/rspamd/lua/:ro - - rspamd-sock:/rspamd-sock - rspamd-vol-1:/var/lib/rspamd restart: always dns: @@ -95,7 +94,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.18 + image: mailcow/phpfpm:1.36 build: ./data/Dockerfiles/phpfpm command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: @@ -103,12 +102,16 @@ services: volumes: - ./data/web:/web:rw - ./data/conf/rspamd/dynmaps:/dynmaps:ro - - rspamd-sock:/rspamd-sock + - rspamd-vol-1:/var/lib/rspamd + - mysql-socket-vol-1:/var/run/mysqld/ + - ./data/conf/sogo/:/etc/sogo/ - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro + - ./data/conf/phpfpm/sogo-sso/:/etc/sogo-sso/ - ./data/conf/phpfpm/php-fpm.d/pools.conf:/usr/local/etc/php-fpm.d/z-pools.conf - ./data/conf/phpfpm/php-conf.d/opcache-recommended.ini:/usr/local/etc/php/conf.d/opcache-recommended.ini - ./data/conf/phpfpm/php-conf.d/upload.ini:/usr/local/etc/php/conf.d/upload.ini - ./data/conf/phpfpm/php-conf.d/other.ini:/usr/local/etc/php/conf.d/zzz-other.ini + - ./data/assets/templates:/tpls environment: - LOG_LINES=${LOG_LINES:-9999} - TZ=${TZ} @@ -127,6 +130,8 @@ services: - API_KEY=${API_KEY:-invalid} - API_ALLOW_FROM=${API_ALLOW_FROM:-invalid} - COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-mailcow-dockerized} + - SKIP_SOLR=${SKIP_SOLR:-y} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} restart: always dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -136,7 +141,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.34 + image: mailcow/sogo:1.54 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} @@ -145,8 +150,15 @@ services: - TZ=${TZ} - LOG_LINES=${LOG_LINES:-9999} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - ACL_ANYONE=${ACL_ANYONE:-disallow} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} volumes: - ./data/conf/sogo/:/etc/sogo/ + - ./data/web/inc/init_db.inc.php:/init_db.inc.php + - ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js + - mysql-socket-vol-1:/var/run/mysqld/ + - sogo-web-vol-1:/sogo_web restart: always dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -157,7 +169,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.33 + image: mailcow/dovecot:1.67 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE @@ -165,15 +177,26 @@ services: - ./data/conf/dovecot:/usr/local/etc/dovecot - ./data/assets/ssl:/etc/ssl/mail/:ro - ./data/conf/sogo/:/etc/sogo/ + - ./data/conf/phpfpm/sogo-sso/:/etc/phpfpm/ - vmail-vol-1:/var/vmail + - vmail-attachments-vol-1:/var/attachments - crypt-vol-1:/mail_crypt/ - - rspamd-sock:/rspamd-sock + - ./data/conf/rspamd/custom/:/etc/rspamd/custom + - ./data/assets/templates:/templates + - rspamd-vol-1:/var/lib/rspamd + - mysql-socket-vol-1:/var/run/mysqld/ environment: - LOG_LINES=${LOG_LINES:-9999} - DBNAME=${DBNAME} - DBUSER=${DBUSER} - DBPASS=${DBPASS} - TZ=${TZ} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} + - MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-1440} + - ACL_ANYONE=${ACL_ANYONE:-disallow} + - SKIP_SOLR=${SKIP_SOLR:-y} + - MAILDIR_SUB=${MAILDIR_SUB:-} ports: - "${DOVEADM_PORT:-127.0.0.1:19991}:12345" - "${IMAP_PORT:-143}:143" @@ -182,6 +205,7 @@ services: - "${POPS_PORT:-995}:995" - "${SIEVE_PORT:-4190}:4190" restart: always + tty: true ulimits: nproc: 65535 nofile: @@ -192,18 +216,20 @@ services: hostname: ${MAILCOW_HOSTNAME} networks: mailcow-network: + ipv4_address: ${IPV4_NETWORK:-172.22.1}.250 aliases: - dovecot postfix-mailcow: - image: mailcow/postfix:1.21 + image: mailcow/postfix:1.31 build: ./data/Dockerfiles/postfix volumes: - ./data/conf/postfix:/opt/postfix/conf - ./data/assets/ssl:/etc/ssl/mail/:ro - postfix-vol-1:/var/spool/postfix - crypt-vol-1:/var/lib/zeyple - - rspamd-sock:/rspamd-sock + - rspamd-vol-1:/var/lib/rspamd + - mysql-socket-vol-1:/var/run/mysqld/ environment: - LOG_LINES=${LOG_LINES:-9999} - TZ=${TZ} @@ -246,6 +272,7 @@ services: envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active && envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active && envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /etc/nginx/conf.d/sogo_eas.active && + . /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo_proxy_auth.active && nginx -qt && until ping phpfpm -c1 > /dev/null; do sleep 1; done && until ping sogo -c1 > /dev/null; do sleep 1; done && @@ -258,12 +285,14 @@ services: - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} - TZ=${TZ} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} volumes: - ./data/web:/web:ro - ./data/conf/rspamd/dynmaps:/dynmaps:ro - ./data/assets/ssl/:/etc/ssl/mail/:ro - ./data/conf/nginx/:/etc/nginx/conf.d/:rw - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro + - sogo-web-vol-1:/usr/lib/GNUstep/SOGo/ ports: - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}" - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}" @@ -278,8 +307,7 @@ services: acme-mailcow: depends_on: - nginx-mailcow - - mysql-mailcow - image: mailcow/acme:1.37 + image: mailcow/acme:1.51 build: ./data/Dockerfiles/acme dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -292,11 +320,14 @@ services: - DBPASS=${DBPASS} - SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n} - SKIP_IP_CHECK=${SKIP_IP_CHECK:-n} + - SKIP_HTTP_VERIFICATION=${SKIP_HTTP_VERIFICATION:-n} + - LE_STAGING=${LE_STAGING:-n} - TZ=${TZ} volumes: - ./data/web/.well-known/acme-challenge:/var/www/acme:rw - ./data/assets/ssl:/var/lib/acme/:rw - ./data/assets/ssl-example:/var/lib/ssl-example/:ro + - mysql-socket-vol-1:/var/run/mysqld/ restart: always networks: mailcow-network: @@ -304,7 +335,7 @@ services: - acme netfilter-mailcow: - image: mailcow/netfilter:1.19 + image: mailcow/netfilter:1.23 build: ./data/Dockerfiles/netfilter stop_grace_period: 30s depends_on: @@ -328,13 +359,14 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.19 + image: mailcow/watchdog:1.40 # Debug #command: /watchdog.sh build: ./data/Dockerfiles/watchdog oom_kill_disable: true volumes: - - rspamd-sock:/rspamd-sock + - rspamd-vol-1:/var/lib/rspamd + - mysql-socket-vol-1:/var/run/mysqld/ restart: always environment: - LOG_LINES=${LOG_LINES:-9999} @@ -346,6 +378,11 @@ services: - WATCHDOG_NOTIFY_EMAIL=${WATCHDOG_NOTIFY_EMAIL} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - IP_BY_DOCKER_API=${IP_BY_DOCKER_API:-0} + - CHECK_UNBOUND=${CHECK_UNBOUND:-1} + - SKIP_CLAMD=${SKIP_CLAMD:-n} + - SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n} + - HTTPS_PORT=${HTTPS_PORT:-443} dns: - ${IPV4_NETWORK:-172.22.1}.254 networks: @@ -354,11 +391,12 @@ services: - watchdog dockerapi-mailcow: - image: mailcow/dockerapi:1.13 + image: mailcow/dockerapi:1.26 restart: always build: ./data/Dockerfiles/dockerapi oom_kill_disable: true environment: + - DBROOT=${DBROOT} - TZ=${TZ} volumes: - /var/run/docker.sock:/var/run/docker.sock:ro @@ -368,7 +406,41 @@ services: aliases: - dockerapi - ipv6nat: + solr-mailcow: + image: mailcow/solr:1.5 + build: ./data/Dockerfiles/solr + restart: always + volumes: + - solr-vol-1:/opt/solr/server/solr/dovecot-fts/data + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + environment: + - TZ=${TZ} + - SOLR_HEAP=${SOLR_HEAP:-1024} + - SKIP_SOLR=${SKIP_SOLR:-y} + networks: + mailcow-network: + aliases: + - solr + + ipv6nat-mailcow: + depends_on: + - unbound-mailcow + - mysql-mailcow + - redis-mailcow + - clamd-mailcow + - rspamd-mailcow + - php-fpm-mailcow + - sogo-mailcow + - dovecot-mailcow + - postfix-mailcow + - memcached-mailcow + - nginx-mailcow + - acme-mailcow + - netfilter-mailcow + - watchdog-mailcow + - dockerapi-mailcow + - solr-mailcow image: robbertkl/ipv6nat restart: always privileged: true @@ -380,6 +452,8 @@ services: networks: mailcow-network: driver: bridge + driver_opts: + com.docker.network.bridge.name: br-mailcow enable_ipv6: true ipam: driver: default @@ -388,10 +462,15 @@ networks: - subnet: ${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64} volumes: + # Storage for email files vmail-vol-1: + # Storage for attachments (deduplicated) + vmail-attachments-vol-1: mysql-vol-1: + mysql-socket-vol-1: redis-vol-1: rspamd-vol-1: + solr-vol-1: postfix-vol-1: crypt-vol-1: - rspamd-sock: + sogo-web-vol-1: diff --git a/generate_config.sh b/generate_config.sh index 7e4ae01c..d241a9ab 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o pipefail @@ -16,6 +16,7 @@ if [ -f mailcow.conf ]; then case $response in [yY][eE][sS]|[yY]) mv mailcow.conf mailcow.conf_backup + chmod 600 mailcow.conf_backup ;; *) exit 1 @@ -48,6 +49,44 @@ while [ -z "${MAILCOW_TZ}" ]; do fi done +MEM_TOTAL=$(awk '/MemTotal/ {print $2}' /proc/meminfo) + +if [ ${MEM_TOTAL} -le "2621440" ]; then + echo "Installed memory is <= 2.5 GiB. It is recommended to disable ClamAV to prevent out-of-memory situations." + echo "ClamAV can be re-enabled by setting SKIP_CLAMD=n in mailcow.conf." + read -r -p "Do you want to disable ClamAV now? [Y/n] " response + case $response in + [nN][oO]|[nN]) + SKIP_CLAMD=n + ;; + *) + SKIP_CLAMD=y + ;; + esac +else + SKIP_CLAMD=n +fi + +if [ ${MEM_TOTAL} -le "2097152" ]; then + echo "Disabling Solr on low-memory system." + SKIP_SOLR=y +elif [ ${MEM_TOTAL} -le "3670016" ]; then + echo "Installed memory is <= 3.5 GiB. It is recommended to disable Solr to prevent out-of-memory situations." + echo "Solr is a prone to run OOM and should be monitored. The default Solr heap size is 1024 MiB and should be set in mailcow.conf according to your expected load." + echo "Solr can be re-enabled by setting SKIP_SOLR=n in mailcow.conf but will refuse to start with less than 2 GB total memory." + read -r -p "Do you want to disable Solr now? [Y/n] " response + case $response in + [nN][oO]|[nN]) + SKIP_SOLR=n + ;; + *) + SKIP_SOLR=y + ;; + esac +else + SKIP_SOLR=n +fi + [ ! -f ./data/conf/rspamd/override.d/worker-controller-password.inc ] && echo '# Placeholder' > ./data/conf/rspamd/override.d/worker-controller-password.inc cat << EOF > mailcow.conf @@ -57,15 +96,18 @@ cat << EOF > mailcow.conf # example.org is _not_ a valid hostname, use a fqdn here. # Default admin user is "admin" # Default password is "moohoo" + MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} # ------------------------------ # SQL database configuration # ------------------------------ + DBNAME=mailcow DBUSER=mailcow # Please use long, random alphanumeric strings (A-Za-z0-9) + DBPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28) DBROOT=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28) @@ -74,6 +116,7 @@ DBROOT=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28) # ------------------------------ # You should use HTTPS, but in case of SSL offloaded reverse proxies: + HTTP_PORT=80 HTTP_BIND=0.0.0.0 @@ -99,57 +142,114 @@ DOVEADM_PORT=127.0.0.1:19991 SQL_PORT=127.0.0.1:13306 # Your timezone + TZ=${MAILCOW_TZ} # Fixed project name + COMPOSE_PROJECT_NAME=mailcowdockerized +# Set this to "allow" to enable the anyone pseudo user. Disabled by default. +# When enabled, ACL can be created, that apply to "All authenticated users" +# This should probably only be activated on mail hosts, that are used exclusivly by one organisation. +# Otherwise a user might share data with too many other users. +ACL_ANYONE=disallow + +# Garbage collector cleanup +# Deleted domains and mailboxes are moved to /var/vmail/_garbage/timestamp_sanitizedstring +# How long should objects remain in the garbage until they are being deleted? (value in minutes) +# Check interval is hourly + +MAILDIR_GC_TIME=1440 + # Additional SAN for the certificate +# +# You can use wildcard records to create specific names for every domain you add to mailcow. +# Example: Add domains "example.com" and "example.net" to mailcow, change ADDITIONAL_SAN to a value like: +#ADDITIONAL_SAN=imap.*,smtp.* +# This will expand the certificate to "imap.example.com", "smtp.example.com", "imap.example.net", "imap.example.net" +# plus every domain you add in the future. +# +# You can also just add static names... +#ADDITIONAL_SAN=srv1.example.net +# ...or combine wildcard and static names: +#ADDITIONAL_SAN=imap.*,srv1.example.com +# + ADDITIONAL_SAN= # Skip running ACME (acme-mailcow, Let's Encrypt certs) - y/n + SKIP_LETS_ENCRYPT=n # Skip IPv4 check in ACME container - y/n + SKIP_IP_CHECK=n +# Skip HTTP verification in ACME container - y/n + +SKIP_HTTP_VERIFICATION=n + # Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n -SKIP_CLAMD=n + +SKIP_CLAMD=${SKIP_CLAMD} + +# Skip Solr on low-memory systems or if you do not want to store a readable index of your mails in solr-vol-1. +SKIP_SOLR=${SKIP_SOLR} + +# Solr heap size in MB, there is no recommendation, please see Solr docs. +# Solr is a prone to run OOM and should be monitored. Unmonitored Solr setups are not recommended. +SOLR_HEAP=1024 # Enable watchdog (watchdog-mailcow) to restart unhealthy containers (experimental) + USE_WATCHDOG=n + +# Allow admins to log into SOGo as email user (without any password) + +ALLOW_ADMIN_EMAIL_LOGIN=n + # Send notifications by mail (no DKIM signature, sent from watchdog@MAILCOW_HOSTNAME) +# Can by multiple rcpts, NO quotation marks + +#WATCHDOG_NOTIFY_EMAIL=a@example.com,b@example.com,c@example.com #WATCHDOG_NOTIFY_EMAIL= # Max log lines per service to keep in Redis logs + LOG_LINES=9999 -# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24) +# Internal IPv4 /24 subnet, format n.n.n (expands to n.n.n.0/24) + IPV4_NETWORK=172.22.1 # Internal IPv6 subnet in fc00::/7 + IPV6_NETWORK=fd4d:6169:6c63:6f77::/64 # Use this IPv4 for outgoing connections (SNAT) + #SNAT_TO_SOURCE= # Use this IPv6 for outgoing connections (SNAT) -#SNAT6_TO_SOURCE= -# Disable IPv6 -# mailcow-network will still be created as IPv6 enabled, all containers will be created -# without IPv6 support. -# Use 1 for disabled, 0 for enabled -SYSCTL_IPV6_DISABLED=0 +#SNAT6_TO_SOURCE= # Create or override API key for web uI # You _must_ define API_ALLOW_FROM, which is a comma separated list of IPs +# API_KEY allowed chars: a-z, A-Z, 0-9, - + #API_KEY= #API_ALLOW_FROM=127.0.0.1,1.2.3.4 +# mail_home is ~/Maildir +MAILDIR_SUB=Maildir + EOF mkdir -p data/assets/ssl +chmod 600 mailcow.conf + # copy but don't overwrite existing certificate cp -n data/assets/ssl-example/*.pem data/assets/ssl/ diff --git a/helper-scripts/backup_and_restore.sh b/helper-scripts/backup_and_restore.sh index 838da1f2..704997c7 100755 --- a/helper-scripts/backup_and_restore.sh +++ b/helper-scripts/backup_and_restore.sh @@ -1,12 +1,16 @@ #!/bin/bash +if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then + BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}" +fi + if [[ ! ${1} =~ (backup|restore) ]]; then echo "First parameter needs to be 'backup' or 'restore'" exit 1 fi -if [[ ${1} == "backup" && ! ${2} =~ (vmail|redis|rspamd|postfix|mysql|all) ]]; then - echo "Second parameter needs to be 'vmail', 'redis', 'rspamd', 'postfix', 'mysql' or 'all'" +if [[ ${1} == "backup" && ! ${2} =~ (crypt|vmail|redis|rspamd|postfix|mysql|all) ]]; then + echo "Second parameter needs to be 'vmail', 'crypt', 'redis', 'rspamd', 'postfix', 'mysql' or 'all'" exit 1 fi @@ -32,7 +36,7 @@ if [[ ! -d ${BACKUP_LOCATION} ]]; then if [[ ! ${CREATE_BACKUP_LOCATION,,} =~ ^(yes|y)$ ]]; then exit 1 else - mkdir ${BACKUP_LOCATION} + mkdir -p ${BACKUP_LOCATION} chmod 755 ${BACKUP_LOCATION} fi else @@ -59,33 +63,39 @@ function backup() { vmail|all) docker run --rm \ -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \ - -v $(docker volume ls -qf name=${CMPS_PRJ}_vmail-vol-1):/vmail \ - debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_vmail.tar.gz /vmail + -v $(docker volume ls -qf name=${CMPS_PRJ}_vmail-vol-1):/vmail:ro \ + debian:stretch-slim /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable -v --best" -Pcvpf /backup/backup_vmail.tar.gz /vmail + ;;& + crypt|all) + docker run --rm \ + -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \ + -v $(docker volume ls -qf name=${CMPS_PRJ}_crypt-vol-1):/crypt:ro \ + debian:stretch-slim /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable -v --best" -Pcvpf /backup/backup_crypt.tar.gz /crypt ;;& redis|all) docker exec $(docker ps -qf name=redis-mailcow) redis-cli save docker run --rm \ -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \ - -v $(docker volume ls -qf name=${CMPS_PRJ}_redis-vol-1):/redis \ - debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_redis.tar.gz /redis + -v $(docker volume ls -qf name=${CMPS_PRJ}_redis-vol-1):/redis:ro \ + debian:stretch-slim /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable -v --best" -Pcvpf /backup/backup_redis.tar.gz /redis ;;& rspamd|all) docker run --rm \ -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \ - -v $(docker volume ls -qf name=${CMPS_PRJ}_rspamd-vol-1):/rspamd \ - debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_rspamd.tar.gz /rspamd + -v $(docker volume ls -qf name=${CMPS_PRJ}_rspamd-vol-1):/rspamd:ro \ + debian:stretch-slim /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable -v --best" -Pcvpf /backup/backup_rspamd.tar.gz /rspamd ;;& postfix|all) docker run --rm \ -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \ - -v $(docker volume ls -qf name=${CMPS_PRJ}_postfix-vol-1):/postfix \ - debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_postfix.tar.gz /postfix + -v $(docker volume ls -qf name=${CMPS_PRJ}_postfix-vol-1):/postfix:ro \ + debian:stretch-slim /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable -v --best" -Pcvpf /backup/backup_postfix.tar.gz /postfix ;;& mysql|all) SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${COMPOSE_FILE}) docker run --rm \ - --network $(docker network ls -qf name=${CMPS_PRJ}_mailcow-network) \ - -v $(docker volume ls -qf name=${CMPS_PRJ}_mysql-vol-1):/var/lib/mysql/ \ + --network $(docker network ls -qf name=${CMPS_PRJ}_) \ + -v $(docker volume ls -qf name=${CMPS_PRJ}_mysql-vol-1):/var/lib/mysql/:ro \ --entrypoint= \ -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \ ${SQLIMAGE} /bin/sh -c "mysqldump -hmysql -uroot -p${DBROOT} --all-databases | gzip > /backup/backup_mysql.gz" @@ -128,6 +138,14 @@ function restore() { debian:stretch-slim /bin/tar -Pxvzf /backup/backup_redis.tar.gz docker start $(docker ps -aqf name=redis-mailcow) ;; + crypt) + docker stop $(docker ps -qf name=dovecot-mailcow) + docker run -it --rm \ + -v ${RESTORE_LOCATION}:/backup \ + -v $(docker volume ls -qf name=${CMPS_PRJ}_crypt-vol-1):/crypt \ + debian:stretch-slim /bin/tar -Pxvzf /backup/backup_crypt.tar.gz + docker start $(docker ps -aqf name=dovecot-mailcow) + ;; rspamd) docker stop $(docker ps -qf name=rspamd-mailcow) docker run -it --rm \ @@ -189,7 +207,7 @@ elif [[ ${1} == "restore" ]]; then echo declare -A FILE_SELECTION RESTORE_POINT="${FOLDER_SELECTION[${input_sel}]}" - if [[ -z $(find "${FOLDER_SELECTION[${input_sel}]}" -maxdepth 1 -type f -regex ".*\(redis\|rspamd\|mysql\|vmail\|postfix\).*") ]]; then + if [[ -z $(find "${FOLDER_SELECTION[${input_sel}]}" -maxdepth 1 -type f -regex ".*\(redis\|rspamd\|mysql\|crypt\|vmail\|postfix\).*") ]]; then echo "No datasets found" exit 1 fi @@ -198,6 +216,10 @@ elif [[ ${1} == "restore" ]]; then echo "[ ${i} ] - Mail directory (/var/vmail)" FILE_SELECTION[${i}]="vmail" ((i++)) + elif [[ ${file} =~ crypt ]]; then + echo "[ ${i} ] - Crypt data" + FILE_SELECTION[${i}]="crypt" + ((i++)) elif [[ ${file} =~ redis ]]; then echo "[ ${i} ] - Redis DB" FILE_SELECTION[${i}]="redis" diff --git a/helper-scripts/ext_sql_sock.docker-compose.override.yml b/helper-scripts/ext_sql_sock.docker-compose.override.yml new file mode 100644 index 00000000..1f052f08 --- /dev/null +++ b/helper-scripts/ext_sql_sock.docker-compose.override.yml @@ -0,0 +1,31 @@ +version: '2.1' +services: + + php-fpm-mailcow: + volumes: + - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock + + sogo-mailcow: + volumes: + - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock + + dovecot-mailcow: + volumes: + - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock + + postfix-mailcow: + volumes: + - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock + + acme-mailcow: + volumes: + - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock + + watchdog-mailcow: + volumes: + - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock + + mysql-mailcow: + image: alpine:3.8 + command: /bin/true + restart: "no" diff --git a/helper-scripts/mailcow-reset-admin.sh b/helper-scripts/mailcow-reset-admin.sh index c0c979b5..74179798 100755 --- a/helper-scripts/mailcow-reset-admin.sh +++ b/helper-scripts/mailcow-reset-admin.sh @@ -19,10 +19,9 @@ read -r -p "Are you sure you want to reset the mailcow administrator account? [y response=${response,,} # tolower if [[ "$response" =~ ^(yes|y)$ ]]; then echo -e "\nWorking, please wait..." - docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM admin;" - docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "INSERT INTO admin (username, password, superadmin, created, modified, active) VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1);" - docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM domain_admins WHERE username='admin';" - docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "INSERT INTO domain_admins (username, domain, created, active) VALUES ('admin', 'ALL', NOW(), 1);" + docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM admin WHERE username='admin';" + docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM domain_admins WHERE username='admin';" + docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "INSERT INTO admin (username, password, superadmin, active) VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, 1);" docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM tfa WHERE username='admin';" echo " Reset credentials: diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 3922f499..3ad9c9b4 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -29,94 +29,131 @@ cd ${SCRIPT_DIR}/../ source mailcow.conf if [[ ${NC_PURGE} == "y" ]]; then + read -r -p "Are you sure you want to purge Nextcloud? [y/N] " response + response=${response,,} + if [[ ! "$response" =~ ^(yes|y)$ ]]; then + echo "OK, aborting." + exit 1 + fi - docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \ - "$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" - docker exec -it $(docker ps -f name=redis-mailcow -q) /bin/sh -c 'redis-cli KEYS "*nextcloud*" | xargs redis-cli DEL' - if [ -d ./data/web/nextcloud/config ]; then - mv ./data/web/nextcloud/config/ ./data/conf/nextcloud-config-folder-$(date +%s).bak - fi - [[ -d ./data/web/nextcloud ]] && rm -rf ./data/web/nextcloud + docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \ + "$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" + docker exec -it $(docker ps -f name=redis-mailcow -q) /bin/sh -c ' cat <<EOF | redis-cli +SELECT 10 +FLUSHDB +EOF +' + if [ -d ./data/web/nextcloud/config ]; then + mv ./data/web/nextcloud/config/ ./data/conf/nextcloud-config-folder-$(date +%s).bak + fi + [[ -d ./data/web/nextcloud ]] && rm -rf ./data/web/nextcloud - [[ -f ./data/conf/nginx/site.nextcloud.custom ]] && mv ./data/conf/nginx/site.nextcloud.custom ./data/conf/nginx/site.nextcloud.custom-$(date +%s).bak - [[ -f ./data/conf/nginx/nextcloud.conf ]] && mv ./data/conf/nginx/nextcloud.conf ./data/conf/nginx/nextcloud.conf-$(date +%s).bak + [[ -f ./data/conf/nginx/site.nextcloud.custom ]] && mv ./data/conf/nginx/site.nextcloud.custom ./data/conf/nginx/site.nextcloud.custom-$(date +%s).bak + [[ -f ./data/conf/nginx/nextcloud.conf ]] && mv ./data/conf/nginx/nextcloud.conf ./data/conf/nginx/nextcloud.conf-$(date +%s).bak docker restart $(docker ps -aqf name=nginx-mailcow) +elif [[ ${NC_UPDATE} == "y" ]]; then + exit; + read -r -p "Are you sure you want to update Nextcloud? [y/N] " response + response=${response,,} + if [[ ! "$response" =~ ^(yes|y)$ ]]; then + echo "OK, aborting." + exit 1 + fi + + if [ ! -f data/web/nextcloud/occ ]; then + echo "Nextcloud occ not found. Is Nextcloud installed?" + exit 1 + fi + if ! grep -q 'installed: true' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then + echo "Nextcloud seems not to be installed." + exit 1 + elif ! grep -q 'version: 15\.' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then + echo "Cannot upgrade to new major version, please update manually." + exit 1 + else + curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ + && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ + && rm nextcloud.tar.bz2 \ + && mkdir -p ./data/web/nextcloud/data \ + && chmod +x ./data/web/nextcloud/occ \ + docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" \ + docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings upgrade" + fi + elif [[ ${NC_INSTALL} == "y" ]]; then + NC_TYPE= + while [[ ! ${NC_TYPE} =~ ^subfolder$|^subdomain$ ]]; do + read -p "Configure as subdomain or subfolder? [subdomain/subfolder] " NC_TYPE + done - NC_TYPE= - while [[ ! ${NC_TYPE} =~ ^subfolder$|^subdomain$ ]]; do - read -p "Configure as subdomain or subfolder? [subdomain/subfolder] " NC_TYPE - done + if [[ ${NC_TYPE} == "subdomain" ]]; then + NC_SUBD= + while [[ -z ${NC_SUBD} ]]; do + read -p "Which subdomain? [format: nextcloud.domain.tld] " NC_SUBD + done + if ! ping -q -c2 ${NC_SUBD} > /dev/null 2>&1 ; then + read -p "Cannot ping subdomain, continue anyway? [y|N] " NC_CONT_FAIL + [[ ! ${NC_CONT_FAIL,,} =~ ^(yes|y)$ ]] && { echo "Ok, exiting..."; exit 1; } + fi + fi + ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28) - if [[ ${NC_TYPE} == "subdomain" ]]; then - NC_SUBD= - while [[ -z ${NC_SUBD} ]]; do - read -p "Which subdomain? [format: nextcloud.domain.tld] " NC_SUBD - done - if ! ping -q -c2 ${NC_SUBD} > /dev/null 2>&1 ; then - read -p "Cannot ping subdomain, continue anyway? [y|N] " NC_CONT_FAIL - [[ ! ${NC_CONT_FAIL,,} =~ ^(yes|y)$ ]] && { echo "Ok, exiting..."; exit 1; } - fi - fi + curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ + && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ + && rm nextcloud.tar.bz2 \ + && mkdir -p ./data/web/nextcloud/data \ + && chmod +x ./data/web/nextcloud/occ - ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28) - - curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-13.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ - && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ - && rm nextcloud.tar.bz2 \ - && rm -rf ./data/web/nextcloud/updater \ - && mkdir -p ./data/web/nextcloud/data \ - && mkdir -p ./data/web/nextcloud/custom_apps \ - && chmod +x ./data/web/nextcloud/occ - - docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud/data /web/nextcloud/config /web/nextcloud/apps /web/nextcloud/custom_apps" - docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ maintenance:install \ - --database mysql \ - --database-host mysql \ - --database-name ${DBNAME} \ - --database-user ${DBUSER} \ - --database-pass ${DBPASS} \ - --database-table-prefix nc_ \ - --admin-user admin \ - --admin-pass ${ADMIN_NC_PASS} \ + docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud/data /web/nextcloud/config /web/nextcloud/apps" + docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings maintenance:install \ + --database mysql \ + --database-host mysql \ + --database-name ${DBNAME} \ + --database-user ${DBUSER} \ + --database-pass ${DBPASS} \ + --database-table-prefix nc_ \ + --admin-user admin \ + --admin-pass ${ADMIN_NC_PASS} \ --data-dir /web/nextcloud/data - docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ config:system:set redis host --value=redis --type=string; \ - /web/nextcloud/occ config:system:set redis port --value=6379 --type=integer; \ - /web/nextcloud/occ config:system:set redis timeout --value=0.0 --type=integer; \ - /web/nextcloud/occ config:system:set memcache.locking --value='\OC\Memcache\Redis' --type=string; \ - /web/nextcloud/occ config:system:set memcache.local --value='\OC\Memcache\Redis' --type=string; \ - /web/nextcloud/occ config:system:set trusted_domains 1 --value=${MAILCOW_HOSTNAME}; \ - /web/nextcloud/occ config:system:set trusted_proxies 0 --value=${IPV6_NETWORK}; \ - /web/nextcloud/occ config:system:set trusted_proxies 1 --value=${IPV4_NETWORK}.0/24; \ - /web/nextcloud/occ config:system:set overwritewebroot --value=/nextcloud; \ - /web/nextcloud/occ config:system:set overwritehost --value=${MAILCOW_HOSTNAME}; \ - /web/nextcloud/occ config:system:set overwriteprotocol --value=https; \ - /web/nextcloud/occ config:system:set mail_smtpmode --value=smtp; \ - /web/nextcloud/occ config:system:set mail_smtpauthtype --value=LOGIN; \ - /web/nextcloud/occ config:system:set mail_from_address --value=nextcloud; \ - /web/nextcloud/occ config:system:set mail_domain --value=${MAILCOW_HOSTNAME}; \ - /web/nextcloud/occ config:system:set mail_smtphost --value=postfix; \ - /web/nextcloud/occ config:system:set mail_smtpport --value=588 - /web/nextcloud/occ app:enable user_external - /web/nextcloud/occ config:system:set user_backends 0 arguments 0 --value={dovecot:143/imap/tls/novalidate-cert} - /web/nextcloud/occ config:system:set user_backends 0 class --value=OC_User_IMAP" + docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings config:system:set redis host --value=redis --type=string; \ + /web/nextcloud/occ --no-warnings config:system:set redis port --value=6379 --type=integer; \ + /web/nextcloud/occ --no-warnings config:system:set redis timeout --value=0.0 --type=integer; \ + /web/nextcloud/occ --no-warnings config:system:set redis dbindex --value=10 --type=integer; \ + /web/nextcloud/occ --no-warnings config:system:set memcache.locking --value='\OC\Memcache\Redis' --type=string; \ + /web/nextcloud/occ --no-warnings config:system:set memcache.local --value='\OC\Memcache\Redis' --type=string; \ + /web/nextcloud/occ --no-warnings config:system:set trusted_domains 1 --value=${MAILCOW_HOSTNAME}; \ + /web/nextcloud/occ --no-warnings config:system:set trusted_proxies 0 --value=${IPV6_NETWORK}; \ + /web/nextcloud/occ --no-warnings config:system:set trusted_proxies 1 --value=${IPV4_NETWORK}.0/24; \ + /web/nextcloud/occ --no-warnings config:system:set overwritewebroot --value=/nextcloud; \ + /web/nextcloud/occ --no-warnings config:system:set overwritehost --value=${MAILCOW_HOSTNAME}; \ + /web/nextcloud/occ --no-warnings config:system:set overwriteprotocol --value=https; \ + /web/nextcloud/occ --no-warnings config:system:set mail_smtpmode --value=smtp; \ + /web/nextcloud/occ --no-warnings config:system:set mail_smtpauthtype --value=LOGIN; \ + /web/nextcloud/occ --no-warnings config:system:set mail_from_address --value=nextcloud; \ + /web/nextcloud/occ --no-warnings config:system:set mail_domain --value=${MAILCOW_HOSTNAME}; \ + /web/nextcloud/occ --no-warnings config:system:set mail_smtphost --value=postfix; \ + /web/nextcloud/occ --no-warnings config:system:set mail_smtpport --value=588 + /web/nextcloud/occ --no-warnings app:install user_external + /web/nextcloud/occ --no-warnings config:system:set user_backends 0 arguments 0 --value={dovecot:143/imap/tls/novalidate-cert} + /web/nextcloud/occ --no-warnings config:system:set user_backends 0 class --value=OC_User_IMAP + /web/nextcloud/occ --no-warnings db:convert-filecache-bigint -n" - if [[ ${NC_TYPE} == "subdomain" ]]; then - docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set trusted_domains 1 --value=${NC_SUBD} - docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritewebroot --value=/ - docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritehost --value=${NC_SUBD} - cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/ - sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf - elif [[ ${NC_TYPE} == "subfolder" ]]; then - cp ./data/assets/nextcloud/site.nextcloud.custom ./data/conf/nginx/ - fi + if [[ ${NC_TYPE} == "subdomain" ]]; then + docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings config:system:set trusted_domains 1 --value=${NC_SUBD} + docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings config:system:set overwritewebroot --value=/ + docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings config:system:set overwritehost --value=${NC_SUBD} + cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/ + sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf + elif [[ ${NC_TYPE} == "subfolder" ]]; then + cp ./data/assets/nextcloud/site.nextcloud.custom ./data/conf/nginx/ + fi docker restart $(docker ps -aqf name=nginx-mailcow) - echo "Login as admin with password: ${ADMIN_NC_PASS}" + echo "Login as admin with password: ${ADMIN_NC_PASS}" fi diff --git a/update.sh b/update.sh index 54fcaa8c..7fc65578 100755 --- a/update.sh +++ b/update.sh @@ -1,8 +1,19 @@ -#!/bin/bash +#!/usr/bin/env bash -#exit on error and pipefail +# Check permissions +if [ "$(id -u)" -ne "0" ]; then + echo "You need to be root" + exit 1 +fi + +# Exit on error and pipefail set -o pipefail +# Add /opt/bin to PATH +PATH=$PATH:/opt/bin + +umask 0022 + for bin in curl docker-compose docker git awk sha1sum; do if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi done @@ -12,7 +23,6 @@ DATE=$(date +%Y-%m-%d_%H_%M_%S) BRANCH=$(git rev-parse --abbrev-ref HEAD) docker_garbage() { - echo -e "\e[32mCollecting garbage...\e[0m" IMGS_TO_DELETE=() for container in $(grep -oP "image: \Kmailcow.+" docker-compose.yml); do REPOSITORY=${container/:*} @@ -61,8 +71,12 @@ while (($#)); do case "${1}" in --check|-c) echo "Checking remote code for updates..." - git fetch origin #${BRANCH} - if [[ -z $(git log HEAD --pretty=format:"%H" | grep $(git rev-parse origin/${BRANCH})) ]]; then + LATEST_REV=$(git ls-remote --exit-code --refs --quiet https://github.com/mailcow/mailcow-dockerized ${BRANCH} | cut -f1) + if [ $? -ne 0 ]; then + echo "A problem occurred while trying to fetch the latest revision from github." + exit 99 + fi + if [[ -z $(git log HEAD --pretty=format:"%H" | grep "${LATEST_REV}") ]]; then echo "Updated code is available." exit 0 else @@ -74,6 +88,7 @@ while (($#)); do MERGE_STRATEGY=ours ;; --gc) + echo -e "\e[32mCollecting garbage...\e[0m" docker_garbage exit 0 ;; @@ -90,6 +105,7 @@ while (($#)); do done [[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing"; exit 1;} +chmod 600 mailcow.conf source mailcow.conf DOTS=${MAILCOW_HOSTNAME//[^.]}; if [ ${#DOTS} -lt 2 ]; then @@ -114,11 +130,17 @@ CONFIG_ARRAY=( "LOG_LINES" "SNAT_TO_SOURCE" "SNAT6_TO_SOURCE" - "SYSCTL_IPV6_DISABLED" "COMPOSE_PROJECT_NAME" "SQL_PORT" "API_KEY" "API_ALLOW_FROM" + "MAILDIR_GC_TIME" + "MAILDIR_SUB" + "ACL_ANYONE" + "SOLR_HEAP" + "SKIP_SOLR" + "ALLOW_ADMIN_EMAIL_LOGIN" + "SKIP_HTTP_VERIFICATION" ) sed -i '$a\' mailcow.conf @@ -128,15 +150,6 @@ for option in ${CONFIG_ARRAY[@]}; do echo "Adding new option \"${option}\" to mailcow.conf" echo "${option}=" >> mailcow.conf fi - elif [[ ${option} == "SYSCTL_IPV6_DISABLED" ]]; then - if ! grep -q ${option} mailcow.conf; then - echo "Adding new option \"${option}\" to mailcow.conf" - echo "# Disable IPv6" >> mailcow.conf - echo "# mailcow-network will still be created as IPv6 enabled, all containers will be created" >> mailcow.conf - echo "# without IPv6 support." >> mailcow.conf - echo "# Use 1 for disabled, 0 for enabled" >> mailcow.conf - echo "SYSCTL_IPV6_DISABLED=0" >> mailcow.conf - fi elif [[ ${option} == "COMPOSE_PROJECT_NAME" ]]; then if ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" @@ -200,6 +213,46 @@ for option in ${CONFIG_ARRAY[@]}; do echo '# Use this IPv6 for outgoing connections (SNAT)' >> mailcow.conf echo "#SNAT6_TO_SOURCE=" >> mailcow.conf fi + elif [[ ${option} == "MAILDIR_GC_TIME" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Garbage collector cleanup' >> mailcow.conf + echo '# Deleted domains and mailboxes are moved to /var/vmail/_garbage/timestamp_sanitizedstring' >> mailcow.conf + echo '# How long should objects remain in the garbage until they are being deleted? (value in minutes)' >> mailcow.conf + echo '# Check interval is hourly' >> mailcow.conf + echo 'MAILDIR_GC_TIME=1440' >> mailcow.conf + fi + elif [[ ${option} == "ACL_ANYONE" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Set this to "allow" to enable the anyone pseudo user. Disabled by default.' >> mailcow.conf + echo '# When enabled, ACL can be created, that apply to "All authenticated users"' >> mailcow.conf + echo '# This should probably only be activated on mail hosts, that are used exclusivly by one organisation.' >> mailcow.conf + echo '# Otherwise a user might share data with too many other users.' >> mailcow.conf + echo 'ACL_ANYONE=disallow' >> mailcow.conf + fi + elif [[ ${option} == "SOLR_HEAP" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Solr heap size, there is no recommendation, please see Solr docs.' >> mailcow.conf + echo '# Solr is a prone to run OOM on large systems and should be monitored. Unmonitored Solr setups are not recommended.' >> mailcow.conf + echo '# Solr will refuse to start with total system memory below or equal to 2 GB.' >> mailcow.conf + echo "SOLR_HEAP=1024" >> mailcow.conf + fi + elif [[ ${option} == "SKIP_SOLR" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Solr is disabled by default after upgrading from non-Solr to Solr-enabled mailcows.' >> mailcow.conf + echo '# Disable Solr or if you do not want to store a readable index of your mails in solr-vol-1.' >> mailcow.conf + echo "SKIP_SOLR=y" >> mailcow.conf + fi + elif [[ ${option} == "MAILDIR_SUB" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf + echo "#MAILDIR_SUB=Maildir" >> mailcow.conf + echo "MAILDIR_SUB=" >> mailcow.conf + fi elif ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" echo "${option}=n" >> mailcow.conf @@ -207,7 +260,7 @@ for option in ${CONFIG_ARRAY[@]}; do done echo -en "Checking internet connection... " -curl -o /dev/null google.com -sm3 +curl -o /dev/null 1.1.1.1 -sm3 if [[ $? != 0 ]]; then echo -e "\e[31mfailed\e[0m" exit 1 @@ -246,6 +299,8 @@ docker-compose down # Silently fixing remote url from andryyy to mailcow git remote set-url origin https://github.com/mailcow/mailcow-dockerized echo -e "\e[32mCommitting current status...\e[0m" +[[ -z "$(git config user.name)" ]] && git config user.name moo +[[ -z "$(git config user.email)" ]] && git config user.email moo@cow.moo git update-index --assume-unchanged data/conf/rspamd/override.d/worker-controller-password.inc git add -u git commit -am "Before update on ${DATE}" > /dev/null @@ -273,7 +328,6 @@ elif [[ ${MERGE_RETURN} != 0 ]]; then exit 1 fi - echo -e "\e[32mFetching new docker-compose version...\e[0m" sleep 2 if [[ ! -z $(which pip) && $(pip list --local | grep -c docker-compose) == 1 ]]; then @@ -315,9 +369,8 @@ if grep -q 'SYSCTL_IPV6_DISABLED=1' mailcow.conf; then read -p "Press any key to continue..." < /dev/tty fi -echo -e "Fixing project name... " +# Checking for old project name bug sed -i 's#COMPOSEPROJECT_NAME#COMPOSE_PROJECT_NAME#g' mailcow.conf -sed -i '/COMPOSE_PROJECT_NAME=/s/-//g' mailcow.conf echo -e "Fixing PHP-FPM worker ports for Nginx sites..." sed -i 's#phpfpm:9000#phpfpm:9002#g' data/conf/nginx/*.conf @@ -325,15 +378,23 @@ if ls data/conf/nginx/*.custom 1> /dev/null 2>&1; then sed -i 's#phpfpm:9000#phpfpm:9002#g' data/conf/nginx/*.custom fi -if [[ -f "data/web/nextcloud/occ" ]]; then -echo "Setting Nextcloud Redis timeout to 0.0..." -docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ config:system:set redis timeout --value=0.0 --type=integer" +# Fix Rspamd maps +if [ -f data/conf/rspamd/custom/global_from_blacklist.map ]; then + mv data/conf/rspamd/custom/global_from_blacklist.map data/conf/rspamd/custom/global_smtp_from_blacklist.map +fi +if [ -f data/conf/rspamd/custom/global_from_whitelist.map ]; then + mv data/conf/rspamd/custom/global_from_whitelist.map data/conf/rspamd/custom/global_smtp_from_whitelist.map fi echo -e "\e[32mStarting mailcow...\e[0m" sleep 2 docker-compose up -d --remove-orphans +if [[ -f "data/web/nextcloud/occ" ]]; then + echo "Setting Nextcloud Redis timeout to 0.0..." + docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ config:system:set redis timeout --value=0.0 --type=integer" +fi + echo -e "\e[32mCollecting garbage...\e[0m" docker_garbage