Initial commit for Autoconfig
parent
d81363541a
commit
7740a0d132
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,157 @@
|
||||
Installation procedure for Autodiscovery configuration tool
|
||||
============================================================
|
||||
* Author: [Jacques Deguest](mailto:jack@deguest.jp)
|
||||
* Created: 2020-03-10
|
||||
* License: Same as Postfix Admin itself
|
||||
|
||||
## Quick background & overview
|
||||
|
||||
Autodiscovery is a somewhat standardised features that makes it possible for mail client to find out the proper configuration for a mail account, and to prevent the every day user from guessing the right parameters.
|
||||
|
||||
Let's take the example of joe@example.com.
|
||||
|
||||
When creating an account on Thurderbird and other who use the same configuration, the mail client will make a http query to <https://www.example.com/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=joe%2540example.com>
|
||||
|
||||
See this page from Mozilla for more information: <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
|
||||
|
||||
If a dns record exist
|
||||
|
||||
For Outlook, the mail client will attempt a POST request to: <https://www.example.com/autodiscover/autodiscover.xml> and submit a xml-based request
|
||||
|
||||
For Mac mail and iOS, the user needs to download a `mobileconfig` file, which is basically an xml file, that can be signed.
|
||||
|
||||
Unfortunately, there is no auto discovery system for Mac/iOS mail, so you need to redirect your users to the `autoconfig.pl` cgi script under the Postfix Admin web root. You need to pass a `emailaddress` parameter for example <https://www.example.com/postfixadmin/autoconfig.pl?mac_mail=1&emailaddress=joe@example.com>
|
||||
|
||||
## Installation
|
||||
|
||||
### Dependencies
|
||||
|
||||
#### SQL
|
||||
|
||||
You need to activate the `uuid-ossp` PostgreSQL extension to use the UUID_V4. You can do that, as an admin logged on PostgreSQL, with `CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`
|
||||
|
||||
If you cannot or do not want to do that, edit the sql script for PostgreSQL and comment line 9 and uncomment line 11, comment line 72 and uncoment line 74, comment line 84 and uncomment line 86, comment line 107 and uncomment line 109
|
||||
|
||||
#### Perl
|
||||
|
||||
The following perl modules are required. Most are standard core modules.
|
||||
|
||||
* strict
|
||||
* IO::File
|
||||
* CGI v4.44
|
||||
* Email::Valid v1.202
|
||||
* Email::Address v1.912
|
||||
* XML::LibXML v2.0201
|
||||
* XML::LibXML::PrettyPrint v0.006
|
||||
* Data::Dumper v2.174
|
||||
* Scalar::Util v1.50
|
||||
* Data::UUID v1.224
|
||||
* File::Basename v2.85
|
||||
* Cwd v3.78
|
||||
* File::Temp v0.2309
|
||||
* File::Spec v3.78
|
||||
* File::Which v1.23
|
||||
* JSON v2.34
|
||||
* DBI v1.642
|
||||
* TryCatch v1.003002
|
||||
* Devel::StackTrace v2.04
|
||||
|
||||
For PostgreSQL you need `DBD::Pg`. I use version 3.8.1.
|
||||
|
||||
For MySQL you need `DBD::mysql` any recent version should do.
|
||||
|
||||
For SQLite, you need `DBD::SQLite`. I used version 1.62.
|
||||
|
||||
You can install those module using `cpanm` (https://metacpan.org/pod/App::cpanminus) like:
|
||||
|
||||
`cpanm --interactive IO::File CGI Email::Valid Email::Address XML::LibXML XML::LibXML::PrettyPrint Data::Dumper Scalar::Util Data::UUID File::Basename Cwd File::Temp File::Spec File::Which JSON DBI TryCatch Devel::StackTrace`
|
||||
|
||||
#### Web
|
||||
|
||||
* jQuery v3.3.1 (loaded automatically from within the template by calling <https://code.jquery.com/jquery-3.3.1.min.js>)
|
||||
|
||||
#### Signature of mobileconfig files for Mac/iOS
|
||||
|
||||
You need to have `openssl` installed. I used version 1.0.2g. You would also need ssl certificates installed for server wide or per domain. I recommend using Let's Encrypt <https://letsencrypt.org/> by installing their command line too `certbot`
|
||||
|
||||
### SQL
|
||||
|
||||
Load the sql script `autoconfig.sql` into your Postfix Admin database. For exaple:
|
||||
|
||||
* PostgreSQL : psql -U postfixadmin postfixadmin < autoconfig.sql
|
||||
|
||||
* MySQL : mysql -u postfixadmin postfixadmin < autoconfig.sql
|
||||
|
||||
* SQLite : sqlite3 /path/to/database.sqlite < autoconfig.sql
|
||||
|
||||
This will create 4 separate tables. Rest assured that `autoconfig` does not alter in any way other areas of Postfix Admin database.
|
||||
|
||||
### PHP, perl and other web files
|
||||
|
||||
Move `AutoconfigHandler.php` under the `model` sub directory in the Postfix Admin root folder, and `autoconfig.php`, `autoconfig.pl`, `autoconfig.css`, `autoconfig.js` and `sprintf.js` under the Postfix Admin web root `public`:
|
||||
|
||||
```
|
||||
mv ./AUTOCONFIG/autoconfig.{css,js,php,pl} ./public/
|
||||
mv ./AUTOCONFIG/{autoconfig_languages.php,sprintf.js} ./public/
|
||||
mv ./AUTOCONFIG/AutoconfigHandler.php ./model
|
||||
mv ./AUTOCONFIG/*.tpl ./templates/
|
||||
```
|
||||
|
||||
#### Additional notes :
|
||||
|
||||
`autoconfig.js` is a small file containing event handlers to make the use of the admin interface smooth, and also makes use of Ajax with jQuery 3.3.1. jQuery 3.3.1 is used, and not the latest 3.3.2, because the pseudo selector `:first` and `:last` have been deprecated and are needed here, at least until I can find an alternative solution. If you have one, please let me know!
|
||||
|
||||
The general use of javascript is light and only to support workflow, nothing more. Pure css is used whenever possible (such as the switch button). No other framework is used to keep things light.
|
||||
|
||||
FontAwesome version 5.12.1 is loaded as import in the css file
|
||||
|
||||
`autoconfig.pl` will guess the location of the `config.inc.php` based on the file path. You can change that, such as by specifiying `config.local.php` instead by editing the perl script and change the line `our $POSTFIXADMIN_CONF_FILE = File::Basename::dirname( __FILE__ ) . '/../config.inc.php';` for example into `our $POSTFIXADMIN_CONF_FILE = '/var/www/postfix/config.inc.php';`
|
||||
|
||||
`autoconfig.pl` will read the php file by converting it into a json file and save that conversion into a temporary file on the server until the modification time of `config.inc.php` changes.
|
||||
|
||||
### DNS
|
||||
|
||||
Not required, but to take full advaantage of the idea of auto discovery, you should set up the following dns record in your domain name zones:
|
||||
|
||||
```bind
|
||||
_submission._tcp IN SRV 0 1 587 mail.example.com.
|
||||
_imap._tcp IN SRV 0 0 143 mail.example.com.
|
||||
_imaps._tcp IN SRV 0 0 993 mail.example.com.
|
||||
_pop3._tcp IN SRV 0 0 110 mail.example.com.
|
||||
```
|
||||
|
||||
If you want to use a dedicated autodisvoer sub domain, you could set up yoru dns zone with the following record:
|
||||
|
||||
```bind
|
||||
_autodiscover._tcp IN SRV 0 10 443 autoconfig.example.com.
|
||||
```
|
||||
|
||||
### Apache
|
||||
|
||||
Add the following tp the general config file or to the relevant Vitual Hosts. You can also add it as a conf file under `/etc/apache2/conf-available` if it exists and then issue `a2enconf autoconfig.conf` to activate it (assuming the file name was `autoconfig.conf`)
|
||||
|
||||
(Here I presumed Postfix Admin is installed under /var/www/postfixadmin)
|
||||
|
||||
```conf
|
||||
Alias /autoconfig /var/www/postfixadmin/public
|
||||
|
||||
<Directory "/var/www/postfixadmin/public/">
|
||||
AllowOverride None
|
||||
Options Indexes FollowSymLinks ExecCGI
|
||||
Require all granted
|
||||
Allow from all
|
||||
AddHandler cgi-script .cgi .pl
|
||||
</Directory>
|
||||
|
||||
RewriteEngine On
|
||||
# For Thunderbird and others
|
||||
RewriteRule "^/.well-known/autoconfig/mail/config-v1.1.xml" "/autoconfig/autoconfig.pl" [PT,L]
|
||||
|
||||
# For Outlook; POST request
|
||||
RewriteRule "^/autodiscover/autodiscover.xml" "/autoconfig/autoconfig.pl?outlook=1" [PT,L]
|
||||
|
||||
# For autodiscovery settings in dns
|
||||
RewriteRule "^/mail/config-v1\.1\.xml(.*)$" "/autoconfig/autoconfig.pl" [PT,L]
|
||||
```
|
||||
|
||||
|
@ -0,0 +1,91 @@
|
||||
<tr class="{if $server.type == 'imap' || $server.type == 'pop3'}autoconfig-incoming{else}autoconfig-outgoing{/if}">
|
||||
<td colspan="3">
|
||||
<table class="server" width="100%">
|
||||
<tr>
|
||||
<td class="label" width="20%"><label>{$PALANG.pAutoconfig_type}:</label></td>
|
||||
{if $server.type == "imap" || $server.type == "pop3"}
|
||||
<td width="60%"><select name="type[]" class="host_type">
|
||||
<option value="imap" {if !empty($server.type) && $server.type == "imap"}selected{/if}>imap</option>
|
||||
<option value="pop3" {if !empty($server.type) && $server.type == "pop3"}selected{/if}>pop3</option></select></td>
|
||||
{else}
|
||||
<td width="20%"><em>smtp</em><input type="hidden" name="type[]" value="smtp" /></td>
|
||||
{/if}
|
||||
<td rowspan="2"><button class="autoconfig-command ripple autoconfig-server-add {if $server.type == 'imap' || $server.type == 'pop3'} autoconfig-incoming-server{else}autoconfig-outgoing-server{/if}" title="{$PALANG.pAutoconfig_add_new_host}"><i class="fas fa-plus fa-2x"></i></button><button class="autoconfig-command ripple autoconfig-server-remove {if $server.type == 'imap' || $server.type == 'pop3'} autoconfig-incoming-server{else}autoconfig-outgoing-server{/if}" title="{$PALANG.pAutoconfig_remove_host}"><i class="fas fa-minus fa-2x"></i></button><button class="autoconfig-command ripple autoconfig-move-up" title="{$PALANG.pAutoconfig_move_up_host}"><i class="fas fa-arrow-up fa-2x"></i></button><button class="autoconfig-command ripple autoconfig-move-down" title="{$PALANG.pAutoconfig_move_down_host}"><i class="fas fa-arrow-down fa-2x"></i></button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_hostname}:</label></td>
|
||||
<td><input type="hidden" name="host_id[]" value="{$server.host_id}" />
|
||||
<input type="text" size="40" class="flat" name="hostname[]" maxlength="255" value="{$server.hostname}" /></td>
|
||||
<!-- <td> </td> -->
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_port}:</label></td>
|
||||
<td><input type="number" class="flat" name="port[]" maxlength="255" value="{$server.port}" list="defaultPorts" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_socket_type}:</label></td>
|
||||
<td><select name="socket_type[]">
|
||||
<option value="" {if empty($server.socket_type) || $server.socket_type == ""}selected{/if}>{$PALANG.pAutoconfig_no_selection}</option>
|
||||
<option value="SSL" {if !empty($server.socket_type) && $server.socket_type == "SSL"}selected{/if}>SSL</option>
|
||||
<option value="STARTTLS" {if !empty($server.socket_type) && $server.socket_type == "STARTTLS"}selected{/if}>STARTTLS</option>
|
||||
<option value="TLS" {if !empty($server.socket_type) && $server.socket_type == "TLS"}selected{/if}>TLS</option>
|
||||
</select></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_auth}:</label></td>
|
||||
<td><select name="auth[]">
|
||||
<option value="password-cleartext" {if !empty($server.auth) && $server.auth == "password-cleartext"}selected{/if}>{$PALANG.pAutoconfig_password_cleartext}</option>
|
||||
<option value="password-encrypted" {if !empty($server.auth) && $server.auth == "password-encrypted"}selected{/if}>{$PALANG.pAutoconfig_password_encrypted}</option>
|
||||
<option value="NTLM" {if !empty($server.auth) && $server.auth == "NTLM"}selected{/if}>NTLM</option>
|
||||
<option value="GSSAPI" {if !empty($server.auth) && $server.auth == "GSSAPI"}selected{/if}>GSSAPI</option>
|
||||
<option value="client-IP-address" {if !empty($server.auth) && $server.auth == "client-IP-address"}selected{/if}>{$PALANG.pAutoconfig_client_ip_address}</option>
|
||||
<option value="TLS-client-cert" {if !empty($server.auth) && $server.auth == "TLS-client-cert"}selected{/if}>{$PALANG.pAutoconfig_tls_client_cert}</option>
|
||||
<option value="smtp-after-pop" {if !empty($server.auth) && $server.auth == "smtp-after-pop"}selected{/if}>{$PALANG.pAutoconfig_smtp_after_pop}</option>
|
||||
<option value="ouath2" {if !empty($server.auth) && $server.auth == "ouath2"}selected{/if}>OAuth2</option>
|
||||
</select></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_username}:</label></td>
|
||||
<td><input type="text" class="flat" name="username[]" maxlength="255" value="{$server.username}" placeholder="%EMAILADDRESS%" /> {$PALANG.pAutoconfig_username_template}<select name="username_template" class="username_template">
|
||||
<option value="">{$PALANG.pAutoconfig_no_selection}</option>
|
||||
<option value="%EMAILADDRESS%">%EMAILADDRESS%</option>
|
||||
<option value="%EMAILLOCALPART%">%EMAILLOCALPART%</option>
|
||||
<option value="%EMAILDOMAIN%">%EMAILDOMAIN%</option>
|
||||
</select></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{if $server.type == "pop3" || $server.type == "imap"}
|
||||
<!-- if incoming server is a pop3 -->
|
||||
{if isset( $server.host_id )}
|
||||
{assign var=host_unique_id value=$server.host_id}
|
||||
{else}
|
||||
{assign var=host_unique_id value=10|mt_rand:20}
|
||||
{/if}
|
||||
<tr class="host_pop3">
|
||||
<td class="label"><label for="autoconfig_leave_messages_on_server_{$host_unique_id}">{$PALANG.pAutoconfig_leave_messages_on_server}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="leave_messages_on_server[]" id="autoconfig_leave_messages_on_server_{$host_unique_id}" value="1" {if !empty($server.leave_messages_on_server) && $server.leave_messages_on_server == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr class="host_pop3">
|
||||
<td class="label"><label for="autoconfig_download_on_biff_{$host_unique_id}">{$PALANG.pAutoconfig_download_on_biff}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="download_on_biff[]" id="autoconfig_download_on_biff_{$host_unique_id}" value="1" {if !empty($server.download_on_biff) && $server.download_on_biff == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr class="host_pop3">
|
||||
<td class="label"><label for="autoconfig_days_to_leave_messages_on_server_{$host_unique_id}">{$PALANG.pAutoconfig_days_to_leave_messages_on_server}:</label></td>
|
||||
<td><input type="number" class="flat" name="days_to_leave_messages_on_server[]" id="autoconfig_days_to_leave_messages_on_server_{$host_unique_id}" min="0" max="365" value="{$server.days_to_leave_messages_on_server}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr class="host_pop3">
|
||||
<td class="label"><label for="autoconfig_check_interval_{$host_unique_id}">{$PALANG.pAutoconfig_check_interval}:</label></td>
|
||||
<td><input type="number" class="flat" name="check_interval[]" id="autoconfig_check_interval_{$host_unique_id}" value="{$server.check_interval}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{/if}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- end if incoming server is a pop3 -->
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
Created on 2020-03-07
|
||||
Copyright 2020 Jacques Deguest
|
||||
Distributed under the same licence as Postfix Admin
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS autoconfig (
|
||||
id SERIAL
|
||||
-- https://stackoverflow.com/questions/43056220/store-uuid-v4-in-mysql
|
||||
,config_id CHAR(36) NOT NULL
|
||||
-- If you prefer you can also use CHAR(36)
|
||||
-- ,config_id CHAR(36) NOT NULL
|
||||
,encoding VARCHAR(12)
|
||||
,provider_id VARCHAR(255) NOT NULL
|
||||
-- Nice feature but not enough standard across other db. Instead, we'll use a separate table
|
||||
-- ,provider_domain VARCHAR(255)[]
|
||||
,provider_name VARCHAR(255)
|
||||
,provider_short VARCHAR(120)
|
||||
-- enable section
|
||||
,enable_status BOOLEAN
|
||||
,enable_url VARCHAR(2048)
|
||||
-- documentation section
|
||||
,documentation_status BOOLEAN
|
||||
,documentation_url VARCHAR(2048)
|
||||
,webmail_login_page VARCHAR(2048)
|
||||
-- webmail login page info
|
||||
,lp_info_url VARCHAR(2048)
|
||||
,lp_info_username VARCHAR(255)
|
||||
,lp_info_username_field_id VARCHAR(255)
|
||||
,lp_info_username_field_name VARCHAR(255)
|
||||
,lp_info_password_field VARCHAR(255)
|
||||
,lp_info_login_button_id VARCHAR(255)
|
||||
,lp_info_login_button_name VARCHAR(255)
|
||||
-- Mac Mail specific fields
|
||||
,account_name VARCHAR(255)
|
||||
-- Typically 'email'
|
||||
,account_type VARCHAR(42)
|
||||
-- could be empty or could be a placeholder like %EMAILADDRESS%
|
||||
,email VARCHAR(255)
|
||||
-- If not explicitly set, this will be guessed from host socket_type
|
||||
,ssl_enabled BOOLEAN
|
||||
-- Will be empty obviously unless the user enters it in the form
|
||||
-- password may be provided by the user on the web interface, but not stored here
|
||||
-- Used for payload_description
|
||||
,description TEXT
|
||||
,organisation VARCHAR(255)
|
||||
-- payload type : regular account, or Microsoft Exchange, e.g. com.apple.mail.managed for mail account or com.apple.eas.account for exchange server
|
||||
,payload_type VARCHAR(100)
|
||||
,prevent_app_sheet BOOLEAN
|
||||
,prevent_move BOOLEAN
|
||||
,smime_enabled BOOLEAN
|
||||
,payload_remove_ok BOOLEAN
|
||||
-- Outlook specific fields
|
||||
-- domain_required -> Not sure this should be an option; false by default
|
||||
,spa BOOLEAN
|
||||
-- payload_enabled
|
||||
,active BOOLEAN
|
||||
-- For signing of the Mac/iOS mobileconfig settings
|
||||
-- none, local or global
|
||||
-- none: do not sign
|
||||
-- local: use this configuration's certificate information
|
||||
-- global: use the server wide one in config.inc.php
|
||||
,sign_option VARCHAR(7)
|
||||
,cert_filepath VARCHAR(1024)
|
||||
,privkey_filepath VARCHAR(1024)
|
||||
,chain_filepath VARCHAR(1024)
|
||||
,created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
,modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
,CONSTRAINT pk_autoconfig PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig UNIQUE (config_id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_domains (
|
||||
id SERIAL
|
||||
,config_id CHAR(36) NOT NULL
|
||||
-- COLLATE here is crucial for the foreign key to work. It must be the same as the target
|
||||
,domain VARCHAR(255) NOT NULL COLLATE latin1_general_ci
|
||||
,CONSTRAINT pk_autoconfig_domains PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_domains UNIQUE (config_id, domain)
|
||||
,CONSTRAINT fk_autoconfig_domains_domain FOREIGN KEY (domain) REFERENCES domain(domain) ON DELETE CASCADE
|
||||
,CONSTRAINT fk_autoconfig_domains_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_hosts (
|
||||
id SERIAL
|
||||
,config_id CHAR(36) NOT NULL
|
||||
-- imap, smtp, pop3
|
||||
,type VARCHAR(12) NOT NULL
|
||||
,hostname VARCHAR(255) NOT NULL
|
||||
,port INTEGER NOT NULL
|
||||
,socket_type VARCHAR(42)
|
||||
,auth VARCHAR(42) DEFAULT 'none' NOT NULL
|
||||
-- possibly to contain some placeholder like %EMAILADDRESS%
|
||||
,username VARCHAR(255)
|
||||
,leave_messages_on_server BOOLEAN DEFAULT FALSE
|
||||
,download_on_biff BOOLEAN DEFAULT FALSE
|
||||
,days_to_leave_messages_on_server INTEGER
|
||||
,check_interval INTEGER
|
||||
,priority INTEGER
|
||||
,CONSTRAINT pk_autoconfig_hosts PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_hosts UNIQUE (config_id, type, hostname, port)
|
||||
,CONSTRAINT fk_autoconfig_hosts_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_text (
|
||||
id SERIAL
|
||||
,config_id CHAR(36) NOT NULL
|
||||
-- instruction or documentation
|
||||
,type VARCHAR(17) NOT NULL
|
||||
-- iso 639 2-letters code
|
||||
,lang CHAR(2) NOT NULL
|
||||
,phrase TEXT
|
||||
,CONSTRAINT pk_autoconfig_text PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_text UNIQUE (config_id, type, lang)
|
||||
,CONSTRAINT fk_autoconfig_text_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
|
||||
) ENGINE = InnoDB;
|
||||
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
Created on 2020-03-07
|
||||
Copyright 2020 Jacques Deguest
|
||||
Distributed under the same licence as Postfix Admin
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS autoconfig (
|
||||
id SERIAL
|
||||
,config_id UUID NOT NULL
|
||||
-- If you prefer not using a UUID field
|
||||
-- ,config_id CHAR(36) NOT NULL
|
||||
,encoding VARCHAR(12)
|
||||
,provider_id VARCHAR(255) NOT NULL
|
||||
-- Nice feature but not enough standard across other db. Instead, we'll use a separate table
|
||||
-- ,provider_domain VARCHAR(255)[]
|
||||
,provider_name VARCHAR(255)
|
||||
,provider_short VARCHAR(120)
|
||||
-- enable section
|
||||
,enable_status BOOLEAN
|
||||
,enable_url VARCHAR(2048)
|
||||
-- documentation section
|
||||
,documentation_status BOOLEAN
|
||||
,documentation_url VARCHAR(2048)
|
||||
,webmail_login_page VARCHAR(2048)
|
||||
-- webmail login page info
|
||||
,lp_info_url VARCHAR(2048)
|
||||
,lp_info_username VARCHAR(255)
|
||||
,lp_info_username_field_id VARCHAR(255)
|
||||
,lp_info_username_field_name VARCHAR(255)
|
||||
,lp_info_password_field VARCHAR(255)
|
||||
,lp_info_login_button_id VARCHAR(255)
|
||||
,lp_info_login_button_name VARCHAR(255)
|
||||
-- Mac Mail specific fields
|
||||
,account_name VARCHAR(255)
|
||||
-- Typically 'email'
|
||||
,account_type VARCHAR(42)
|
||||
-- could be empty or could be a placeholder like %EMAILADDRESS%
|
||||
,email VARCHAR(255)
|
||||
-- If not explicitly set, this will be guessed from host socket_type
|
||||
,ssl_enabled BOOLEAN
|
||||
-- Will be empty obviously unless the user enters it in the form
|
||||
-- password may be provided by the user on the web interface, but not stored here
|
||||
-- Used for payload_description
|
||||
,description TEXT
|
||||
,organisation VARCHAR(255)
|
||||
-- payload type : regular account, or Microsoft Exchange, e.g. com.apple.mail.managed for mail account or com.apple.eas.account for exchange server
|
||||
,payload_type VARCHAR(100)
|
||||
,prevent_app_sheet BOOLEAN
|
||||
,prevent_move BOOLEAN
|
||||
,smime_enabled BOOLEAN
|
||||
,payload_remove_ok BOOLEAN
|
||||
-- Outlook specific fields
|
||||
-- domain_required -> Not sure this should be an option; false by default
|
||||
,spa BOOLEAN
|
||||
-- payload_enabled
|
||||
,active BOOLEAN
|
||||
-- For signing of the Mac/iOS mobileconfig settings
|
||||
-- none, local or global
|
||||
-- none: do not sign
|
||||
-- local: use this configuration's certificate information
|
||||
-- global: use the server wide one in config.inc.php
|
||||
,sign_option VARCHAR(7)
|
||||
,cert_filepath VARCHAR(1024)
|
||||
,privkey_filepath VARCHAR(1024)
|
||||
,chain_filepath VARCHAR(1024)
|
||||
,created TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
,modified TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
,CONSTRAINT pk_autoconfig PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig UNIQUE (config_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_domains (
|
||||
id SERIAL
|
||||
,config_id UUID NOT NULL
|
||||
-- If you prefer not to install uuid-ossp:
|
||||
--- ,config_id CHAR(36) NOT NULL
|
||||
,domain VARCHAR(255) NOT NULL
|
||||
,CONSTRAINT pk_autoconfig_domains PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_domains UNIQUE (config_id, domain)
|
||||
,CONSTRAINT fk_autoconfig_domains_domain FOREIGN KEY (domain) REFERENCES public.domain(domain) ON DELETE CASCADE
|
||||
,CONSTRAINT fk_autoconfig_domains_config_id FOREIGN KEY (config_id) REFERENCES public.autoconfig(config_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_hosts (
|
||||
id SERIAL
|
||||
,config_id UUID NOT NULL
|
||||
-- If you prefer not to install uuid-ossp:
|
||||
--- ,config_id CHAR(36) NOT NULL
|
||||
-- imap, smtp, pop3
|
||||
,type VARCHAR(12) NOT NULL
|
||||
,hostname VARCHAR(255) NOT NULL
|
||||
,port INTEGER NOT NULL
|
||||
,socket_type VARCHAR(42)
|
||||
,auth VARCHAR(42) DEFAULT 'none' NOT NULL
|
||||
-- possibly to contain some placeholder like %EMAILADDRESS%
|
||||
,username VARCHAR(255)
|
||||
,leave_messages_on_server BOOLEAN DEFAULT FALSE
|
||||
,download_on_biff BOOLEAN DEFAULT FALSE
|
||||
,days_to_leave_messages_on_server INTEGER
|
||||
,check_interval INTEGER
|
||||
,priority INTEGER
|
||||
,CONSTRAINT pk_autoconfig_hosts PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_hosts UNIQUE (config_id, type, hostname, port)
|
||||
,CONSTRAINT fk_autoconfig_hosts_config_id FOREIGN KEY (config_id) REFERENCES public.autoconfig(config_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_text (
|
||||
id SERIAL
|
||||
,config_id UUID NOT NULL
|
||||
-- If you prefer not to install uuid-ossp:
|
||||
--- ,config_id CHAR(36) NOT NULL
|
||||
-- instruction or documentation
|
||||
,type VARCHAR(17) NOT NULL
|
||||
-- iso 639 2-letters code
|
||||
,lang CHAR(2) NOT NULL
|
||||
,phrase TEXT
|
||||
,CONSTRAINT pk_autoconfig_text PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_text UNIQUE (config_id, type, lang)
|
||||
,CONSTRAINT fk_autoconfig_text_config_id FOREIGN KEY (config_id) REFERENCES public.autoconfig(config_id) ON DELETE CASCADE
|
||||
);
|
||||
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
Created on 2020-03-07
|
||||
Copyright 2020 Jacques Deguest
|
||||
Distributed under the same licence as Postfix Admin
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS autoconfig (
|
||||
id INTEGER NOT NULL
|
||||
,config_id CHAR(36) NOT NULL
|
||||
,encoding VARCHAR(12)
|
||||
,provider_id VARCHAR(255) NOT NULL
|
||||
-- Nice feature but not enough standard across other db. Instead, we'll use a separate table
|
||||
-- ,provider_domain VARCHAR(255)[]
|
||||
,provider_name VARCHAR(255)
|
||||
,provider_short VARCHAR(120)
|
||||
-- enable section
|
||||
,enable_url VARCHAR(2048)
|
||||
,enable_status BOOLEAN
|
||||
-- documentation section
|
||||
,documentation_status BOOLEAN
|
||||
,documentation_url VARCHAR(2048)
|
||||
,webmail_login_page VARCHAR(2048)
|
||||
-- webmail login page info
|
||||
,lp_info_url VARCHAR(2048)
|
||||
,lp_info_username VARCHAR(255)
|
||||
,lp_info_username_field_id VARCHAR(255)
|
||||
,lp_info_username_field_name VARCHAR(255)
|
||||
,lp_info_password_field VARCHAR(255)
|
||||
,lp_info_login_button_id VARCHAR(255)
|
||||
,lp_info_login_button_name VARCHAR(255)
|
||||
-- Mac Mail specific fields
|
||||
,account_name VARCHAR(255)
|
||||
-- Typically 'email'
|
||||
,account_type VARCHAR(42)
|
||||
-- could be empty or could be a placeholder like %EMAILADDRESS%
|
||||
,email VARCHAR(255)
|
||||
-- If not explicitly set, this will be guessed from host socket_type
|
||||
,ssl_enabled BOOLEAN
|
||||
-- Will be empty obviously unless the user enters it in the form
|
||||
-- password may be provided by the user on the web interface, but not stored here
|
||||
-- Used for payload_description
|
||||
,description TEXT
|
||||
,organisation VARCHAR(255)
|
||||
-- payload type : regular account, or Microsoft Exchange, e.g. com.apple.mail.managed for mail account or com.apple.eas.account for exchange server
|
||||
,payload_type VARCHAR(100)
|
||||
,prevent_app_sheet BOOLEAN
|
||||
,prevent_move BOOLEAN
|
||||
,smime_enabled BOOLEAN
|
||||
,payload_remove_ok BOOLEAN
|
||||
-- Outlook specific fields
|
||||
-- domain_required -> Not sure this should be an option; false by default
|
||||
,spa BOOLEAN
|
||||
-- payload_enabled
|
||||
,active BOOLEAN
|
||||
-- For signing of the Mac/iOS mobileconfig settings
|
||||
-- none, local or global
|
||||
-- none: do not sign
|
||||
-- local: use this configuration's certificate information
|
||||
-- global: use the server wide one in config.inc.php
|
||||
,sign_option VARCHAR(7)
|
||||
,cert_filepath VARCHAR(1024)
|
||||
,privkey_filepath VARCHAR(1024)
|
||||
,chain_filepath VARCHAR(1024)
|
||||
,created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
,modified TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
,CONSTRAINT pk_autoconfig PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig UNIQUE (config_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_domains (
|
||||
id INTEGER NOT NULL
|
||||
,config_id CHAR(36) NOT NULL
|
||||
,domain VARCHAR(255) NOT NULL
|
||||
,CONSTRAINT pk_autoconfig_domains PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_domains UNIQUE (config_id, domain)
|
||||
,CONSTRAINT fk_autoconfig_domains_domain FOREIGN KEY (domain) REFERENCES domain(domain) ON DELETE CASCADE
|
||||
,CONSTRAINT fk_autoconfig_domains_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_hosts (
|
||||
id INTEGER NOT NULL
|
||||
,config_id CHAR(36) NOT NULL
|
||||
-- imap, smtp, pop3
|
||||
,type VARCHAR(12) NOT NULL
|
||||
,hostname VARCHAR(255) NOT NULL
|
||||
,port INTEGER NOT NULL
|
||||
,socket_type VARCHAR(42)
|
||||
,auth VARCHAR(42) DEFAULT 'none' NOT NULL
|
||||
-- possibly to contain some placeholder like %EMAILADDRESS%
|
||||
,username VARCHAR(255)
|
||||
,leave_messages_on_server BOOLEAN DEFAULT FALSE
|
||||
,download_on_biff BOOLEAN DEFAULT FALSE
|
||||
,days_to_leave_messages_on_server INTEGER
|
||||
,check_interval INTEGER
|
||||
,priority INTEGER
|
||||
,CONSTRAINT pk_autoconfig_hosts PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_hosts UNIQUE (config_id, type, hostname, port)
|
||||
,CONSTRAINT fk_autoconfig_hosts_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autoconfig_text (
|
||||
id INTEGER NOT NULL
|
||||
,config_id CHAR(36) NOT NULL
|
||||
-- instruction or documentation
|
||||
,type VARCHAR(17) NOT NULL
|
||||
-- iso 639 2-letters code
|
||||
,lang CHAR(2) NOT NULL
|
||||
,phrase TEXT
|
||||
,CONSTRAINT pk_autoconfig_text PRIMARY KEY (id)
|
||||
,CONSTRAINT idx_autoconfig_text UNIQUE (config_id, type, lang)
|
||||
,CONSTRAINT fk_autoconfig_text_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
|
||||
);
|
||||
|
@ -0,0 +1,908 @@
|
||||
/*
|
||||
Created on 2020-03-12
|
||||
Copyright 2020 Jacques Deguest
|
||||
Distributed under the same licence as Postfix Admin
|
||||
*/
|
||||
$(document).ready(function()
|
||||
{
|
||||
const DEBUG = true;
|
||||
|
||||
// Credits to: https://tdanemar.wordpress.com/2010/08/24/jquery-serialize-method-and-checkboxes/
|
||||
// Modified by Jacques Deguest to include other form elements:
|
||||
// http://www.w3schools.com/tags/tag_input.asp
|
||||
(function($)
|
||||
{
|
||||
$.fn.serializeAll = function(options)
|
||||
{
|
||||
return $.param(this.serializeArrayAll(options));
|
||||
};
|
||||
|
||||
$.fn.serializeArrayAll = function (options)
|
||||
{
|
||||
var o = $.extend({
|
||||
checkboxesAsBools: false
|
||||
}, options || {});
|
||||
|
||||
var rselectTextarea = /select|textarea/i;
|
||||
var rinput = /text|hidden|password|search|date|time|number|color|datetime|email|file|image|month|range|tel|url|week/i;
|
||||
|
||||
return this.map(function ()
|
||||
{
|
||||
return this.elements ? $.makeArray(this.elements) : this;
|
||||
})
|
||||
.filter(function ()
|
||||
{
|
||||
return this.name && !this.disabled &&
|
||||
(this.checked
|
||||
|| (o.checkboxesAsBools && this.type === 'checkbox')
|
||||
|| rselectTextarea.test(this.nodeName)
|
||||
|| rinput.test(this.type));
|
||||
})
|
||||
.map(function (i, elem)
|
||||
{
|
||||
var val = $(this).val();
|
||||
return val == null ?
|
||||
null :
|
||||
$.isArray(val) ?
|
||||
$.map(val, function (val, i)
|
||||
{
|
||||
return { name: elem.name, value: val };
|
||||
}) :
|
||||
{
|
||||
name: elem.name,
|
||||
value: (o.checkboxesAsBools && this.type === 'checkbox') ?
|
||||
(this.checked ? 1 : 0) :
|
||||
val
|
||||
};
|
||||
}).get();
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
window.makeMessage = function( type, mesg )
|
||||
{
|
||||
return( sprintf( '<div class="%s">%s</div>', type, mesg ) );
|
||||
};
|
||||
|
||||
window.showMessage = function()
|
||||
{
|
||||
if( DEBUG ) console.log( "Called from " + ( arguments.callee.caller === null ? 'void' : arguments.callee.caller.name ) );
|
||||
var opts = {
|
||||
div: $('#message'),
|
||||
append: false,
|
||||
dom: null,
|
||||
timeout: null,
|
||||
scroll: false,
|
||||
timeoutCallback: null,
|
||||
};
|
||||
var msgDiv = $('#message');
|
||||
if( arguments.length == 3 && typeof( arguments[2] ) === 'object' )
|
||||
{
|
||||
var param = arguments[2];
|
||||
opts.type = arguments[0];
|
||||
opts.message = arguments[1];
|
||||
if( typeof( param.append ) !== 'undefined' ) opts.append = param.append;
|
||||
if( typeof( param.dom ) !== 'undefined' ) opts.dom = param.dom;
|
||||
if( typeof( param.timeout ) !== 'undefined' ) opts.timeout = param.timeout;
|
||||
if( typeof( param.timeoutCallback ) !== 'undefined' ) opts.timeoutCallback = param.timeoutCallback;
|
||||
if( typeof( param.scroll ) !== 'undefined' ) opts.scroll = param.scroll;
|
||||
if( typeof( param.speak ) !== 'undefined' ) opts.speak = param.speak;
|
||||
}
|
||||
// Backward compatibility
|
||||
else if( arguments.length >= 2 )
|
||||
{
|
||||
opts.type = arguments[0];
|
||||
opts.message = arguments[1];
|
||||
if( arguments.length > 2 ) opts.append = arguments[2];
|
||||
if( arguments.length > 3 ) opts.dom = arguments[3];
|
||||
if( arguments.length > 4 ) opts.timeout = arguments[4];
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDiv.append( makeMessage( 'warning', "showMessage called with only " + arguments.length + " parameters while 2 at minimum are required. Usage: showMessage( type, message, append, domObject, timeout ) or showMessage( type, message, options )" ) );
|
||||
return( false );
|
||||
}
|
||||
if( typeof( opts.dom ) === 'object' && opts.dom != null )
|
||||
{
|
||||
msgDiv = opts.dom;
|
||||
}
|
||||
// Check if message is an array of messages
|
||||
// https://stackoverflow.com/a/4775741/4814971
|
||||
if( Array.isArray )
|
||||
{
|
||||
if( Array.isArray( opts.message ) )
|
||||
{
|
||||
opts.messages = opts.message;
|
||||
}
|
||||
}
|
||||
else if( opts.message instanceof( Array ) )
|
||||
{
|
||||
opts.messages = opts.message;
|
||||
}
|
||||
else if( $.isArray( opts.message ) )
|
||||
{
|
||||
opts.messages = opts.message;
|
||||
}
|
||||
|
||||
// messages has been set with previous check
|
||||
// Make this array a list of errors
|
||||
if( opts.hasOwnProperty( 'messages' ) )
|
||||
{
|
||||
opts.message = sprintf( "<ol>\n%s\n</ol>", opts.messages.map(function(e){ return('<li>' + e + '</li>'); }).join( "\n" ) );
|
||||
}
|
||||
|
||||
if( opts.append )
|
||||
{
|
||||
msgDiv.append(makeMessage(opts.type, opts.message));
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDiv.html(makeMessage(opts.type, opts.message));
|
||||
}
|
||||
|
||||
if( opts.type == 'error' )
|
||||
{
|
||||
msgDiv.addClass( 'error-shake' );
|
||||
setTimeout(function()
|
||||
{
|
||||
msgDiv.removeClass( 'error-shake' );
|
||||
}, 70000);
|
||||
}
|
||||
|
||||
if( parseInt( opts.timeout ) > 0 )
|
||||
{
|
||||
var thisTimeout = parseInt( opts.timeout );
|
||||
setTimeout(function()
|
||||
{
|
||||
msgDiv.html( '' );
|
||||
if( typeof( opts.timeoutCallback ) === 'function' )
|
||||
{
|
||||
opts.timeoutCallback();
|
||||
}
|
||||
},thisTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
setTimeout(function()
|
||||
{
|
||||
msgDiv.html( '' );
|
||||
},15000);
|
||||
}
|
||||
if( opts.scroll )
|
||||
{
|
||||
if( DEBUG ) console.log( "Scrolling to the top of the page..." );
|
||||
$('html, body').animate( { scrollTop: 0 }, 500 );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.log( "No scrolling..." );
|
||||
}
|
||||
};
|
||||
|
||||
window.postfixAdminProgressBar = function()
|
||||
{
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
if( DEBUG ) console.log( "Initiating the progress bar." );
|
||||
if( DEBUG ) console.log( "Called from:\n" + (new Error).stack );
|
||||
$('#postfixadmin-progress').show().removeClass('done');
|
||||
xhr.upload.addEventListener('progress', function(evt)
|
||||
{
|
||||
if( evt.lengthComputable )
|
||||
{
|
||||
var percentComplete = evt.loaded / evt.total;
|
||||
if( DEBUG ) console.log(percentComplete);
|
||||
$('#postfixadmin-progress').css({
|
||||
width: percentComplete * 100 + '%' });
|
||||
if( DEBUG ) console.log( "upload.addEventListener: " + percentComplete );
|
||||
if( percentComplete === 1 )
|
||||
{
|
||||
$('#postfixadmin-progress').addClass('done').hide();
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
xhr.addEventListener('progress', function(evt)
|
||||
{
|
||||
if( evt.lengthComputable )
|
||||
{
|
||||
var percentComplete = evt.loaded / evt.total;
|
||||
if( DEBUG ) console.log("addEventListener: " + percentComplete);
|
||||
$('#postfixadmin-progress').css({
|
||||
width: percentComplete * 100 + '%' });
|
||||
if( percentComplete === 1 )
|
||||
{
|
||||
$('#postfixadmin-progress').addClass('done').hide();
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
return( xhr );
|
||||
};
|
||||
|
||||
window.postfixAdminProgressBarStart = function()
|
||||
{
|
||||
// $('#postfixadmin-progress').show().addClass('done');
|
||||
$('#postfixadmin-progress').show();
|
||||
$({property: 0}).animate({property: 85},
|
||||
{
|
||||
// Arbitrary time, which should well cover the time it takes to get response from server
|
||||
// Otherwise, well our progress bar will hang at 85% until we get a call to kill it
|
||||
duration: 4000,
|
||||
step: function()
|
||||
{
|
||||
var _percent = Math.round( this.property );
|
||||
$('#postfixadmin-progress').css( 'width', _percent + '%' );
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.postfixAdminProgressBarStop = function()
|
||||
{
|
||||
$({property: 85}).animate({property: 105},
|
||||
{
|
||||
duration: 1000,
|
||||
step: function()
|
||||
{
|
||||
var _percent = Math.round( this.property );
|
||||
$('#postfixadmin-progress').css( 'width', _percent + '%' );
|
||||
if( _percent == 105 )
|
||||
{
|
||||
$('#postfixadmin-progress').addClass('done');
|
||||
}
|
||||
},
|
||||
complete: function()
|
||||
{
|
||||
$('#postfixadmin-progress').hide();
|
||||
$('#postfixadmin-progress').removeClass('done');
|
||||
$('#postfixadmin-progress').css( 'width', '0%' );
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.autoconfigShowHideArrow = function()
|
||||
{
|
||||
// Reset. Show them all
|
||||
$('table.server .autoconfig-command').show();
|
||||
// Hide first and last ones
|
||||
// This requires jQUery up to v3.3. Version 3.4 onward do not support :first and :last anymore
|
||||
$('.autoconfig-incoming:first .autoconfig-move-up').hide();
|
||||
$('.autoconfig-incoming:last .autoconfig-move-down').hide();
|
||||
$('.autoconfig-outgoing:first .autoconfig-move-up').hide();
|
||||
$('.autoconfig-outgoing:last .autoconfig-move-down').hide();
|
||||
};
|
||||
|
||||
window.autoconfig_ajax_call = function( postData )
|
||||
{
|
||||
var prom = $.Deferred();
|
||||
$this = $(this);
|
||||
$.ajax({
|
||||
xhr: postfixAdminProgressBar(),
|
||||
type: "POST",
|
||||
url: "autoconfig.php",
|
||||
dataType: "json",
|
||||
data: postData,
|
||||
beforeSend: function(xhr)
|
||||
{
|
||||
xhr.overrideMimeType( "application/json; charset=utf-8" );
|
||||
postfixAdminProgressBarStart();
|
||||
},
|
||||
error: function(xhr, errType, ExceptionObject)
|
||||
{
|
||||
postfixAdminProgressBarStop();
|
||||
if( DEBUG ) console.log( "Returned error " + xhr.status + " with error type " + errType + " and exception object " + JSON.stringify( ExceptionObject ) );
|
||||
if( DEBUG ) console.log( "Current url is: " + xhr.responseURL );
|
||||
if( DEBUG ) console.log( "Http headers are: " + xhr.getAllResponseHeaders() );
|
||||
if( DEBUG ) console.log( "Response raw data is: \n" + xhr.responseText );
|
||||
// There was a redirect most likely due to some timeout
|
||||
// if( xhr.getResponseHeader( 'Content-Type' ).toLowerCase().indexOf( 'text/html' ) >= 0 )
|
||||
// {
|
||||
// window.location.reload();
|
||||
// return( true );
|
||||
// }
|
||||
var msg = 'An unexpected error has occurred';
|
||||
showMessage( 'error', msg, { scroll: true });
|
||||
$this.addClass( 'error-shake' );
|
||||
prom.reject();
|
||||
},
|
||||
success: function(data, status, xhr)
|
||||
{
|
||||
postfixAdminProgressBarStop();
|
||||
if( data.error )
|
||||
{
|
||||
showMessage( 'error', data.error, { scroll: true });
|
||||
$this.addClass( 'error-shake' );
|
||||
setTimeout(function()
|
||||
{
|
||||
$this.removeClass( 'error-shake' );
|
||||
},5000);
|
||||
prom.reject();
|
||||
}
|
||||
else
|
||||
{
|
||||
if( data.success )
|
||||
{
|
||||
prom.resolve(data);
|
||||
showMessage( 'success', data.success, { scroll: true });
|
||||
if( DEBUG ) console.log( "save(): " + data.success );
|
||||
}
|
||||
else if( data.info )
|
||||
{
|
||||
showMessage( 'info', data.info, { scroll: true } );
|
||||
prom.resolve();
|
||||
}
|
||||
else
|
||||
{
|
||||
showMessage( 'info', data.msg, { scroll: true } );
|
||||
prom.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return( prom.promise() );
|
||||
};
|
||||
|
||||
$(document).on('click','#autoconfig_save', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
$this = $(this);
|
||||
var data = {handler: 'autoconfig_save'};
|
||||
$('#autoconfig_form').serializeArrayAll().map(function(item)
|
||||
{
|
||||
if( data[ item.name ] !== undefined )
|
||||
{
|
||||
if( !data[ item.name ].push )
|
||||
{
|
||||
data[ item.name ] = [ data[item.name] ];
|
||||
}
|
||||
data[ item.name ].push( item.value );
|
||||
}
|
||||
else
|
||||
{
|
||||
data[ item.name ] = item.value;
|
||||
}
|
||||
});
|
||||
if( DEBUG ) console.log( "serialized data is: " + JSON.stringify( data ) );
|
||||
autoconfig_ajax_call( data ).done(function(data)
|
||||
{
|
||||
// Since this shared function is used for both saving (adding and updating) as well as deleting
|
||||
// we check if those data properties are returned by the server.
|
||||
// Those here are only returned if this is the result of an addition
|
||||
if( data.hasOwnProperty('config_id') )
|
||||
{
|
||||
if( $('#autoconfig_form input[name="config_id"]').length == 0 )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find the field \"config_id\" !" );
|
||||
}
|
||||
if( typeof( data.config_id ) !== 'undefined' && data.config_id.length > 0 )
|
||||
{
|
||||
// We force trigger change, because this is a hidden field and it hidden field do not trigger change
|
||||
$('#autoconfig_form input[name="config_id"]').val( data.config_id ).trigger('change');
|
||||
}
|
||||
}
|
||||
// Do the hosts
|
||||
var hostTypes = [ 'incoming', 'outgoing' ];
|
||||
for( var j = 0; j < hostTypes.length; j++ )
|
||||
{
|
||||
var hostType = hostTypes[j];
|
||||
if( DEBUG ) console.log( "Checking host of type " + hostType );
|
||||
if( data.hasOwnProperty(hostType + '_server') && Array.isArray( data[hostType + '_server'] ) )
|
||||
{
|
||||
var thoseHosts = $('.autoconfig-' + hostType);
|
||||
var dataHosts = data[hostType + '_server'];
|
||||
if( thoseHosts.length == 0 )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find any hosts block in our form!" );
|
||||
}
|
||||
else if( dataHosts.length != thoseHosts.length )
|
||||
{
|
||||
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Total of " + hostType + " servers returned from the server (" + dataHosts.length + ") do not match the total hosts we have in our form (" + thoseHosts.length + ")." );
|
||||
}
|
||||
dataHosts.forEach(function(def, index)
|
||||
{
|
||||
if( DEBUG ) console.log( "def contains: " + JSON.stringify( def ) );
|
||||
if( !def.hasOwnProperty('config_id') ||
|
||||
!def.hasOwnProperty('hostname') ||
|
||||
!def.hasOwnProperty('port') )
|
||||
{
|
||||
if( DEBUG ) console.error( "Something is wrong. Data received is missing fields config_id, or hostname or port" );
|
||||
return( false );
|
||||
}
|
||||
thoseHosts.each(function(offset, obj)
|
||||
{
|
||||
var dom = $(obj);
|
||||
var hostId = dom.find('input[name="host_id[]"]');
|
||||
var hostName = dom.find('input[name="hostname[]"]');
|
||||
var hostPort = dom.find('input[name="port[]"]');
|
||||
if( !hostId.length )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find host id field for host type \"" + hostType + "\" at offset " + i );
|
||||
}
|
||||
if( !hostName.length )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find host name field for host type \"" + hostType + "\" at offset " + i );
|
||||
}
|
||||
if( !hostPort.length )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find host port field for host type \"" + hostType + "\" at offset " + i );
|
||||
}
|
||||
// We found our match: no id, hostname and port match
|
||||
if( hostId.val().length == 0 && hostName.val() == def.hostname && hostPort.val() == def.port )
|
||||
{
|
||||
if( DEBUG ) console.log( "Setting host id " + def.host_id + " to host name " + hostName.val() + " with port " + hostPort.val() );
|
||||
hostId.val( def.host_id );
|
||||
// exit the loop
|
||||
return( false );
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.error( "Something is wrong. Data received for hosts of type " + hostType + " does not exist or is not an array." );
|
||||
}
|
||||
}
|
||||
|
||||
// Now, do the texts
|
||||
var textTypes = ['instruction', 'documentation'];
|
||||
for( var j = 0; j < textTypes.length; j++ )
|
||||
{
|
||||
var textType = textTypes[j];
|
||||
if( DEBUG ) console.log( "Checking text of type " + textType );
|
||||
if( data.hasOwnProperty(textType) && Array.isArray( data[textType] ) )
|
||||
{
|
||||
var dataTexts = data[textType];
|
||||
var thoseTextIds = $('input[name="' + textType + '_id[]"]');
|
||||
var thoseTextLang = $('select[name="' + textType + '_lang[]"]');
|
||||
var thoseTextData = $('textarea[name="' + textType + '_text[]"]');
|
||||
// The array could very well be empty
|
||||
dataTexts.forEach(function(def, index)
|
||||
{
|
||||
if( !def.hasOwnProperty('id') ||
|
||||
!def.hasOwnProperty('type') ||
|
||||
!def.hasOwnProperty('lang') )
|
||||
{
|
||||
if( DEBUG ) console.error( "Something is wrong. Data received is missing fields id, or type or lang" );
|
||||
return( false );
|
||||
}
|
||||
for( var k = 0; k < thoseTextIds.length; k++ )
|
||||
{
|
||||
var textId = thoseTextIds.eq(k);
|
||||
var textLang = thoseTextLang.eq(k);
|
||||
// Found a match
|
||||
if( textId.val().length == 0 && textLang.val() == def.lang )
|
||||
{
|
||||
if( DEBUG ) console.log( "Setting text id " + def.id + " to text with type " + textType + " and language " + def.lang );
|
||||
textId.val( def.id );
|
||||
return( false );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.error( "Something is wrong. Data received for " + textType + " text does not exist or is not an array." );
|
||||
}
|
||||
}
|
||||
}).fail(function()
|
||||
{
|
||||
// Nothing for now
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click','#autoconfig_remove', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var data =
|
||||
{
|
||||
handler: 'autoconfig_remove',
|
||||
config_id: $('input[name="config_id"]').val(),
|
||||
token: $('input[name="token"]').val(),
|
||||
};
|
||||
if( DEBUG ) console.log( "Data to be sent is: " + JSON.stringify( data ) );
|
||||
autoconfig_ajax_call( data ).done(function(data)
|
||||
{
|
||||
// Reload the page, but without the query string at the end
|
||||
window.location.href = window.location.pathname;
|
||||
}).fail(function()
|
||||
{
|
||||
// Nothing for now
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#autoconfig_cancel', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
window.location.href = 'list.php?table=domain';
|
||||
return( true );
|
||||
});
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
|
||||
window.getRandomInt = function(min, max)
|
||||
{
|
||||
return( Math.floor( Math.random() * Math.floor((max - min) + min) ) );
|
||||
}
|
||||
/*
|
||||
I could have just created one function and called it
|
||||
*/
|
||||
// Add and remove hosts
|
||||
$(document).on('click','.autoconfig-server-add',function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('table.server').closest('tr');
|
||||
if( !row.length )
|
||||
{
|
||||
throw( "Unable to find the current enclosing row." );
|
||||
}
|
||||
var clone = row.clone();
|
||||
clone.find('select,input[type!="hidden"],textarea').each(function(i,item)
|
||||
{
|
||||
$(item).val( '' );
|
||||
});
|
||||
// We need to remove the host_id so it can be treated as a new host and not an update of an existing one
|
||||
clone.find('input[name="host_id[]"]').val( '' );
|
||||
// Set default value and trigger change, which will call an event handler that will hide/show the section on pop3
|
||||
clone.find('.host_type').val('imap').trigger('change');
|
||||
var optionLabels = ['leave_messages_on_server', 'download_on_biff', 'days_to_leave_messages_on_server', 'check_interval'];
|
||||
optionLabels.forEach(function(fieldName, index)
|
||||
{
|
||||
if( DEBUG ) console.log( "Checking field name " + fieldName );
|
||||
var thisField = clone.find('input[name="' + fieldName + '[]"]');
|
||||
if( thisField.length > 0 )
|
||||
{
|
||||
var forThisField = $('label[for="' + thisField.attr('id') + '"]', clone);
|
||||
if( forThisField.length > 0 )
|
||||
{
|
||||
if( DEBUG ) console.log( "Field is " + thisField.html() + "\nLabel field is: " + forThisField.html() );
|
||||
thisField.attr('id', 'autoconfig_' + fieldName + '_' + getRandomInt(100,1000));
|
||||
maxAttempt = 10;
|
||||
if( DEBUG ) console.log( "Checking if generated id exists: " + thisField.attr('id') );
|
||||
while( $('#' + thisField.attr('id'), clone).length > 0 && ++maxAttempt < 10 )
|
||||
{
|
||||
thisField.attr('id', 'autoconfig_' + fieldName + '_' + getRandomInt(100,1000));
|
||||
}
|
||||
forThisField.attr('for', thisField.attr('id'));
|
||||
if( DEBUG ) console.log( "Final generated id is: " + thisField.attr('id') );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.error( "Unable to find label element for field name." );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
clone.insertAfter( row );
|
||||
autoconfigShowHideArrow();
|
||||
$('html, body').animate( { scrollTop: clone.offset().top }, 500 );
|
||||
return( true );
|
||||
});
|
||||
|
||||
$(document).on('click','.autoconfig-server-remove',function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('table.server').closest('tr');
|
||||
if( !row.length )
|
||||
{
|
||||
throw( "Unable to find the current enclosing row." );
|
||||
}
|
||||
// Check if there are at least 2 elements, so that after at least one remain
|
||||
var re = new RegExp('(autoconfig-(?:incoming|outgoing))-server');
|
||||
var res = $(this).attr('class').match( re );
|
||||
console.log( res );
|
||||
if( res == null )
|
||||
{
|
||||
throw( "Cannot find class \"autoconfig-incoming-server\" or class \"autoconfig-outgoing-server\" in our clicked element." );
|
||||
}
|
||||
// Now find how many elements we have with this class
|
||||
var total = $('tr.' + res[1]).length;
|
||||
if( total < 2 )
|
||||
{
|
||||
row.addClass('autoconfig-error-shake');
|
||||
setTimeout(function()
|
||||
{
|
||||
row.removeClass('autoconfig-error-shake');
|
||||
},1000);
|
||||
return( false );
|
||||
}
|
||||
row.remove();
|
||||
autoconfigShowHideArrow();
|
||||
});
|
||||
|
||||
// Add and remove account enable instructions or support documentation
|
||||
$(document).on('click','.autoconfig-locale-text-add',function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('tr');
|
||||
if( !row.length )
|
||||
{
|
||||
throw( "Unable to find the current enclosing row." );
|
||||
}
|
||||
var clone = row.clone();
|
||||
clone.find('select,input[type!="hidden"],textarea').each(function(i,item)
|
||||
{
|
||||
$(item).val( '' );
|
||||
});
|
||||
// We need to remove the host_id so it can be treated as a new text and not an update of an existing one
|
||||
clone.find('input[name$="_id[]"]').val( '' );
|
||||
clone.insertAfter( row );
|
||||
$('html, body').animate( { scrollTop: clone.offset().top }, 500 );
|
||||
});
|
||||
|
||||
$(document).on('click','.autoconfig-locale-text-remove',function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('tr');
|
||||
if( !row.length )
|
||||
{
|
||||
throw( "Unable to find the current enclosing row." );
|
||||
}
|
||||
var re = new RegExp('(autoconfig-(instruction|documentation))');
|
||||
var res = $(this).attr('class').match( re );
|
||||
if( res == null )
|
||||
{
|
||||
throw( "Cannot find class \"autoconfig-instruction\" or class \"autoconfig-documentation\" in our clicked element." );
|
||||
}
|
||||
var textType = res[2];
|
||||
var total = $('tr.' + res[1]).length;
|
||||
if( DEBUG ) console.log( total + " rows found for text type " + textType );
|
||||
if( total < 2 )
|
||||
{
|
||||
if( DEBUG ) console.log( "text remove: one left" );
|
||||
if( DEBUG ) console.log( "Getting lang object with " + '[name="' + textType + '_lang[]"]' );
|
||||
var textLang = row.find('[name="' + textType + '_lang[]"]');
|
||||
if( DEBUG ) console.log( "Getting text object with " + '[name="' + textType + '_text[]"]' );
|
||||
var textData = row.find('[name="' + textType + '_text[]"]');
|
||||
if( DEBUG ) console.log( "text remove: found lang and text field? " + ( ( textLang.length > 0 && textData.length > 0 ) ? "Yes" : "No" ) );
|
||||
// This is remaining default tet row and there is no more data
|
||||
if( ( textLang.val() === null || ( textLang.val() !== null && textLang.val() == '' ) ) && $.trim(textData.val()) == '' )
|
||||
{
|
||||
if( DEBUG ) console.log( "text remove: lang and text fields are empty already, error shake it" );
|
||||
row.addClass('autoconfig-error-shake');
|
||||
setTimeout(function()
|
||||
{
|
||||
row.removeClass('autoconfig-error-shake');
|
||||
},1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.log( "text remove: empty fields" );
|
||||
textLang.val( '' );
|
||||
textData.val( '' );
|
||||
}
|
||||
return( false );
|
||||
}
|
||||
row.remove();
|
||||
});
|
||||
|
||||
$(document).on('click', '#copy_provider_value', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var orgField = $('input[name="organisation"]');
|
||||
var providerNameField = $('input[name="provider_name"]');
|
||||
if( !orgField.length || !providerNameField.length )
|
||||
{
|
||||
throw( "Unable to find either the provider name field or the organisation field!" );
|
||||
}
|
||||
if( providerNameField.val().length == 0 )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
orgField.val( providerNameField.val() );
|
||||
return( true );
|
||||
});
|
||||
|
||||
$(document).on('click', '#autoconfig_toggle_select_all_domains', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
// if( DEBUG ) console.log( "provider_domain options length: " + $('#autoconfig_provider_domain option').length + " and disabled options are: " + $('#autoconfig_provider_domain option:disabled').length + " and selected options are: " + $('#autoconfig_provider_domain option:selected').length );
|
||||
if( $('#autoconfig_provider_domain option:selected').length > 0 )
|
||||
{
|
||||
$('#autoconfig_provider_domain option').prop('selected', false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if( $('#autoconfig_provider_domain option:disabled').length == $('#autoconfig_provider_domain option').length )
|
||||
{
|
||||
var row = $(this).closest('tr');
|
||||
row.addClass('error-shake');
|
||||
setTimeout(function()
|
||||
{
|
||||
row.removeClass('error-shake');
|
||||
},500);
|
||||
return( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#autoconfig_provider_domain option:not(:disabled)').prop('selected', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('change', '#autoconfig_form .host_type', function(e)
|
||||
{
|
||||
var typeValue = $(this).val();
|
||||
// We get the enclosing table object to set some limiting context to the host_pop3 selector below
|
||||
var tbl = $(this).closest('table');
|
||||
if( typeValue == 'imap' )
|
||||
{
|
||||
$('.host_pop3', tbl).hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
$('.host_pop3', tbl).show();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('change', '#autoconfig_form .username_template', function(e)
|
||||
{
|
||||
var usernameField = $(this).closest('.server').find('input[name="username[]"]');
|
||||
if( usernameField.length == 0 )
|
||||
{
|
||||
throw( "Unable to find the username field!" );
|
||||
}
|
||||
usernameField.val( $(this).val() );
|
||||
});
|
||||
|
||||
$(document).on('change', '#autoconfig_form select[name="jump_to"]', function(e)
|
||||
{
|
||||
var id = $(this).val();
|
||||
var re = new RegExp( '([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})' );
|
||||
// This will display an empty form
|
||||
if( id.length == 0 )
|
||||
{
|
||||
window.location.href = 'autoconfig.php';
|
||||
}
|
||||
else if( id.match( re ) )
|
||||
{
|
||||
window.location.href = 'autoconfig.php?config_id=' + encodeURIComponent( id );
|
||||
}
|
||||
});
|
||||
|
||||
// Upon change in the content of the hidden config_id, enable or disable the "Remove" buttton
|
||||
// The Remove button can only be used if this is for an existing configuration obviously
|
||||
$(document).on('change', '#autoconfig_form input[name="config_id"]', function(e)
|
||||
{
|
||||
if( DEBUG ) console.log( "Config id field contains \"" + $(this).val() + "\"." );
|
||||
if( $(this).val().length == 0 || $(this).val().match( /^[[:blank:]\t]*$/ ) )
|
||||
{
|
||||
$('#autoconfig_remove').attr( 'disabled', true );
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#autoconfig_remove').attr( 'disabled', false );
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.autoconfig-move-up', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('.server').closest('tr');
|
||||
if( row.prev().length == 0 || ( !row.prev().hasClass('autoconfig-incoming') && !row.prev().hasClass('autoconfig-outgoing') ) )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
row.insertBefore( row.prev() );
|
||||
$('html, body').animate( { scrollTop: row.offset().top }, 500 );
|
||||
autoconfigShowHideArrow();
|
||||
});
|
||||
|
||||
$(document).on('click', '.autoconfig-move-down', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('.server').closest('tr');
|
||||
if( row.next().length == 0 || ( !row.next().hasClass('autoconfig-incoming') && !row.next().hasClass('autoconfig-outgoing') ) )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
row.insertAfter( row.next() );
|
||||
$('html, body').animate( { scrollTop: row.offset().top }, 500 );
|
||||
autoconfigShowHideArrow();
|
||||
});
|
||||
|
||||
window.checkExistingTextLanguage = function(opts = {})
|
||||
{
|
||||
if( typeof( opts ) !== 'object' )
|
||||
{
|
||||
throw( "Parameters provided is not an object. Call checkExistingTextLanguage like this: checkExistingTextLanguage({ type: 'instruction', lang: 'fr', caller: $(this) })" );
|
||||
}
|
||||
var textType = opts.type;
|
||||
var callerMenu = opts.caller;
|
||||
var langToCheck = opts.lang;
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() checking text type " + textType + " for language " + langToCheck );
|
||||
if( typeof( textType ) === 'undefined' )
|
||||
{
|
||||
throw( "No text type was provided." );
|
||||
}
|
||||
var langMenu = $('select[name="' + textType + '_lang[]"]');
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() Found " + langMenu.length + " language menu(s)." );
|
||||
if( langMenu.length == 0 )
|
||||
{
|
||||
throw( "Could not find any language menu for this text type " + textType );
|
||||
}
|
||||
if( langMenu.length == 1 )
|
||||
{
|
||||
return( true );
|
||||
}
|
||||
var alreadySelected = false;
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() checking each existing language menu." );
|
||||
langMenu.each(function(offset, menuObject)
|
||||
{
|
||||
if( $(menuObject).is( callerMenu ) )
|
||||
{
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() skipping because this is our caller menu." );
|
||||
return( true );
|
||||
}
|
||||
// Found a match, stop there
|
||||
else if( $(menuObject).val() == langToCheck )
|
||||
{
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() Found match with this menu value (" + $(menuObject).val() + ") matching the language to check \"" + langToCheck + "\"." );
|
||||
alreadySelected = true;
|
||||
return( false );
|
||||
}
|
||||
});
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() returning: " + !alreadySelected );
|
||||
return( !alreadySelected );
|
||||
};
|
||||
|
||||
// Upon selection or change of a language, we check it has not been selected already
|
||||
$(document).on('change', 'select[name="instruction_lang[]"]', function(e)
|
||||
{
|
||||
if( !checkExistingTextLanguage({ type: 'instruction', lang: $(this).val(), caller: $(this) }) )
|
||||
{
|
||||
$(this).addClass('error-shake');
|
||||
// <i class="fas fa-exclamation-triangle"></i>
|
||||
var warning = $('<i/>',
|
||||
{
|
||||
class: 'fas fa-exclamation-triangle fa-2x',
|
||||
style: 'color: red; font-size: 20px;',
|
||||
});
|
||||
warning.insertAfter( $(this) );
|
||||
var that = $(this);
|
||||
setTimeout(function()
|
||||
{
|
||||
that.removeClass('error-shake');
|
||||
warning.remove();
|
||||
},5000);
|
||||
$(this).val('');
|
||||
return( false );
|
||||
}
|
||||
return( true );
|
||||
});
|
||||
|
||||
window.toggleCertFiles = function(option)
|
||||
{
|
||||
if( typeof( option ) === 'undefined' )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
if( option == 'local' )
|
||||
{
|
||||
$('.cert_files').show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$('.cert_files').hide();
|
||||
}
|
||||
};
|
||||
|
||||
$(document).on('change', 'select[name="sign_option"]', function(e)
|
||||
{
|
||||
toggleCertFiles( $(this).val() );
|
||||
});
|
||||
|
||||
// Need to trigger the change for the host type menu
|
||||
if( $('#autoconfig_form .host_type').length > 0 )
|
||||
{
|
||||
$('#autoconfig_form .host_type').trigger('change');
|
||||
}
|
||||
if( $('#autoconfig_form input[name="config_id"]').length > 0 )
|
||||
{
|
||||
$('#autoconfig_form input[name="config_id"]').trigger('change');
|
||||
}
|
||||
// Hide useless up/down arrows
|
||||
autoconfigShowHideArrow();
|
||||
toggleCertFiles( $('select[name="sign_option"]').val() );
|
||||
});
|
@ -0,0 +1,808 @@
|
||||
/*
|
||||
Created on 2020-03-11
|
||||
Copyright 2020 Jacques Deguest
|
||||
Distributed under the same licence as Postfix Admin
|
||||
*/
|
||||
@charset "UTF-8";
|
||||
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css");
|
||||
|
||||
/* Progress bar */
|
||||
#postfixadmin-progress
|
||||
{
|
||||
/* position: absolute; */
|
||||
position: fixed;
|
||||
display: none;
|
||||
z-index: 2147483647;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 0px;
|
||||
height: 2px;
|
||||
-webkit-transition: width 500ms ease-out,opacity 400ms linear;
|
||||
-moz-transition: width 500ms ease-out,opacity 400ms linear;
|
||||
-ms-transition: width 500ms ease-out,opacity 400ms linear;
|
||||
-o-transition: width 500ms ease-out,opacity 400ms linear;
|
||||
transition: width 500ms ease-out,opacity 400ms linear;
|
||||
-webkit-border-radius: 1px;
|
||||
-moz-border-radius: 1px;
|
||||
border-radius: 1px;
|
||||
background: #b91f1f;
|
||||
}
|
||||
|
||||
#postfixadmin-progress.done
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#postfixadmin-progress dd,
|
||||
#postfixadmin-progress dt
|
||||
{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
height: 2px;
|
||||
-webkit-border-radius: 100%;
|
||||
-moz-border-radius: 100%;
|
||||
border-radius: 100%;
|
||||
-webkit-box-shadow: #b91f1f 1px 0 6px 1px;
|
||||
-moz-box-shadow: #b91f1f 1px 0 6px 1px;
|
||||
-ms-box-shadow: #b91f1f 1px 0 6px 1px;
|
||||
box-shadow: #b91f1f 1px 0 6px 1px;
|
||||
}
|
||||
|
||||
#postfixadmin-progress dd
|
||||
{
|
||||
right: 0px;
|
||||
clip: rect(-6px,22px,14px,10px);
|
||||
width: 20px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#postfixadmin-progress dt
|
||||
{
|
||||
right: -80px;
|
||||
clip: rect(-6px,90px,14px,-6px);
|
||||
width: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@-moz-keyframes legaltech-progress-pulse
|
||||
{
|
||||
30%
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
60%
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
100%
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-ms-keyframes legaltech-progress-pulse
|
||||
{
|
||||
30%
|
||||
{
|
||||
opacity: .6;
|
||||
}
|
||||
60%
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
100%
|
||||
{
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes legaltech-progress-pulse
|
||||
{
|
||||
30%
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
60%
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
100%
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes legaltech-progress-pulse
|
||||
{
|
||||
30%
|
||||
{
|
||||
opacity: .6;
|
||||
}
|
||||
60%
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
100%
|
||||
{
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes legaltech-progress-pulse
|
||||
{
|
||||
30%
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
60%
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
100%
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#postfixadmin-progress.waiting dd,
|
||||
#postfixadmin-progress.waiting dt
|
||||
{
|
||||
-webkit-animation: legaltech-progress-pulse 2s ease-out 0s infinite;
|
||||
-moz-animation: legaltech-progress-pulse 2s ease-out 0s infinite;
|
||||
-ms-animation: legaltech-progress-pulse 2s ease-out 0s infinite;
|
||||
-o-animation: legaltech-progress-pulse 2s ease-out 0s infinite;
|
||||
animation: legaltech-progress-pulse 2s ease-out 0s infinite;
|
||||
}
|
||||
|
||||
.autoconfig-command
|
||||
{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
z-index: 1;
|
||||
outline: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border-radius: 50%;
|
||||
padding: 3px 3px;
|
||||
/* padding: 0 16px; */
|
||||
margin: 10px 4px;
|
||||
background-color: #3f51b5;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
-webkit-transition: all .15s ease-in;
|
||||
transition: all .15s ease-in;
|
||||
}
|
||||
|
||||
.autoconfig-command:hover,
|
||||
.autoconfig-command:focus
|
||||
{
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
@-webkit-keyframes ripple
|
||||
{
|
||||
0%
|
||||
{
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
opacity: .5;
|
||||
}
|
||||
100%
|
||||
{
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ripple
|
||||
{
|
||||
0%
|
||||
{
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
opacity: .5;
|
||||
}
|
||||
100%
|
||||
{
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ripple:before
|
||||
{
|
||||
content: '';
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
-webkit-transform: translate( -50%, -50% );
|
||||
transform: translate( -50%, -50% );
|
||||
border-radius: 50%;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
.ripple:not(:active):before
|
||||
{
|
||||
-webkit-animation: ripple 0.4s cubic-bezier( 0, 0, 0.2, 1 );
|
||||
animation: ripple 0.4s cubic-bezier( 0, 0, 0.2, 1 );
|
||||
-webkit-transition: visibility .4s step-end;
|
||||
transition: visibility .4s step-end;
|
||||
}
|
||||
|
||||
.ripple:active:before
|
||||
{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.autoconfig-server-add,
|
||||
.autoconfig-locale-text-add
|
||||
{
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.autoconfig-server-remove,
|
||||
.autoconfig-locale-text-remove
|
||||
{
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
@-webkit-keyframes autoconfig-error
|
||||
{
|
||||
0% { -webkit-transform: translateX( 0px ); }
|
||||
25% { -webkit-transform: translateX( 30px ); }
|
||||
45% { -webkit-transform: translateX( -30px ); }
|
||||
65% { -webkit-transform: translateX( 30px ); }
|
||||
82% { -webkit-transform: translateX( -30px ); }
|
||||
94% { -webkit-transform: translateX( 30px ); }
|
||||
35%, 55%, 75%, 87%, 97%, 100% { -webkit-transform: translateX( 0px ); }
|
||||
}
|
||||
|
||||
@keyframes autoconfig-error
|
||||
{
|
||||
0% { transform: translateX( 0px ); }
|
||||
25% { transform: translateX( 30px ); }
|
||||
45% { transform: translateX( -30px ); }
|
||||
65% { transform: translateX( 30px ); }
|
||||
82% { transform: translateX( -30px ); }
|
||||
94% { transform: translateX( 30px ); }
|
||||
35%, 55%, 75%, 87%, 97%, 100% { transform: translateX( 0px ); }
|
||||
}
|
||||
|
||||
.autoconfig-error-shake
|
||||
{
|
||||
-webkit-animation: autoconfig-error 0.35s linear;
|
||||
-moz-animation: autoconfig-error 0.35s linear;
|
||||
animation: autoconfig-error 0.35s linear;
|
||||
}
|
||||
|
||||
.autoconfig-error:before
|
||||
{
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
/* Message formatting */
|
||||
#message
|
||||
{
|
||||
max-width: 90%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* So that any link inside a message can be clickable */
|
||||
#message a
|
||||
{
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.success,
|
||||
.error,
|
||||
.info,
|
||||
.warning,
|
||||
.edit,
|
||||
.lock,
|
||||
.tip,
|
||||
.download,
|
||||
.chat,
|
||||
.task
|
||||
{
|
||||
position: relative;
|
||||
display: block;
|
||||
clear: both;
|
||||
margin-bottom: 2px;
|
||||
padding: 10px 10px 10px 40px;
|
||||
min-height: 20px;
|
||||
font-family: "Lucida Grande", "Lucida Sans Unicode", sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 20px;
|
||||
-webkit-border-radius: 5px;
|
||||
-khtml-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.5) inset;
|
||||
-moz-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.5) inset;
|
||||
-o-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.5) inset;
|
||||
box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.5) inset;
|
||||
pointer-events: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.success:before,
|
||||
.error:before,
|
||||
.info:before,
|
||||
.warning:before,
|
||||
.edit:before,
|
||||
.lock:before,
|
||||
.tip:before,
|
||||
.download:before,
|
||||
.chat:before,
|
||||
.task:before
|
||||
{
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 16px;
|
||||
width: 14px;
|
||||
height: 15px;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAACWCAYAAADufbn/AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo5QkZCREFGMkQ5NDFFMTExQUFEMUYxOUI5RkM4REUzOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyM0E3OTNDMjQ2NUYxMUUxQTMxRkIyNkE5NjhGNzcwOSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyM0E3OTNDMTQ2NUYxMUUxQTMxRkIyNkE5NjhGNzcwOSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozM0NDRDBCOTVENDZFMTExOTJFNEYxQUZFREQ5ODZCMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QkZCREFGMkQ5NDFFMTExQUFEMUYxOUI5RkM4REUzOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pmz0zwcAAAlJSURBVHja7JgLUBRHGoCnZ2Zhl+WdZYGFhUOkFA4C6CW+lYRTcyGA5nEmPjg9E8ipJ4gFxkRFTVQOJV6qLoUYS3MqnuQiENEk5PCxQixNTorkAAkJMQiiiEAE5bHs7tzfyyyZnZ3dnSUmd1V3XfVv93b3N//03393/9OIYRiiIiw0iyCIdJBN879ufpfgJWifANkpkCsgKdBHiz4ePy4T/uRz+q3gwkYIIQ3BML7GCoQqoJxAkhLJWp6CQ9B5uSCEE8PMByaMVD39TAGiKK0A/JoFBMnj4ej35zY0tlFvX7jwtba7u7+3vm4GdKI4fR4HceVDU0+UbIFiOwk/t8O35BSqFy95Q0CzEHTVOFRsVTYpa156Mafz/LlVfEiqUn02R1O13ASZgdgQoLGK0et9LNQhZIBhLANrHzMDBa1nmfTsHBph0hrkEhys4Y0ZG+4w9F9sBBFJvseH3KOiSmZVns0MXvH7rVbgIBKefI0PTSsp2wzFmgkbXnmHD9Nubq1BKSkDoIyJqpo/7xNwPebiwuQT8D8Cj5sjisbcXRsrJoQNVU6Kab7zaXUy1NGmxui+r75aJACNwr2Njc9BPgfECddx51ECMmzDqmbtXNChRIvtuG53BPbdAyCnQTJosRAi0GkYrRT+rgEZIEVAT3AgY5LLvCJJEdBJLuQidf9macKfdtNsh5WQ4X0lZ29WwwBblwTQ+wBJftDk2ZT27P4MtV9kNcrIC38N6t4wmphAn0LHuVDEmorNIa/G1GcL1wX5RZ7B00KTiMoyMPqR7YRgZgDwGdbOhVxdvBtWLTqU4a8IO2+aS3JSRMJfECJ1o3sRwURaQL89mA7QOa4DUCeKqq923W3T3exsmgaYmbHc5Iov05ccS/d9aJwG/urM1jbrOf7FH29efbmudAPDGGgTtPaFo+kKr6AqdhGbJ44j+xVX5Gxfvyfq/rZ98Rc7u1vioI6y4vQWvuoHEgPShdejoKYf6+QkMcY06uSz3yzMgWwryPcXMtO8HNEYx+ae2HMcWY9b3aXSvZODAjTsareduCbedroypr69Yx6U1damwWw6YHzB8Ax8mMYhgmjRZKaF2dl/CBKgaMhrTWP0dJH1YluJMU4ZRZKtXi6yL3FFmFJxCW8NYsCYTb95LLenf+BhXDEtJKgOslt2QZizu7kVmkHjOSihb09Sqzr2VV3WiprHIZ1ulnF8Mtm3q4o/2PG3z2uP2psSkwNgxyZu9fZNHdAOK1+c8ehh+PuIGAf43vSqaTOn7HgmNrId/taJAZcnRE58fsO8ORjoB/kcpNcWyD903Nm1aDc9mEOnan0gjjhwLLMQJICtvgFSCrJ9Vn5bp4VGgHDnQhBjuIIoyV3jItAPe7B9MZQGcOkoCNAyHBSMPg2gKTlXluDy5W2TizgwTikAH6EBCoJwpYDgjJWSSHskcu+PTGUdF0RoPzAakqSd/giQnLdGDZAZhS1zG6WYAc9BSQ6bFJFPkQa9NsRRzqDThpBw4IxlIimacpa36QZ6x5k9cXjAn51TeCvK32Lypa7XSZnPuHMWG5hBLwPrXcFiLPPjV8UvNGTA7Jf2IYruMx87PTg5+9xqLLhs1gZ9A+NeLqB9YpO/6Gupyb1RffB1MPXI+kTEcN3+JfWmMlZiCnj9py7N84lJqjW5nE9z2ZZVNy8eWcfxEj0n1DRq8p+ekh+6YHsBjuPNYvL77Q0zr53akQD5r3RD94xGoZ1db8pVEf8MSdz0odw/HB+yt4WWFV6TahAc+Jq86T5IB0jrmILA0p0on92bnl74KnOXFAmlQpY58hGDiu0erABQICtnp1RrZG4BNSMuI52I662eRiU7CArkOAhTupM80tfVNPvMO9GnbjSW4EgZCY7R+ESCKAJZ9MMyJP+6YKN+DxS/ARmkrUCHuRBFy7omzHi1jLvX0gLQIZDFXChq7t6skNi0SsFdjgMt40KxCQfWqX+5GG9Q9yzODhY6YAZJXDpZ6AQf4mrE0HIuNPmpdzMCwp8rY48Ey9MKa1OGzG1EaOQziJLIO1io1BpketWP3BQRZyPjd693lvvWPZJclM5CNo9zVLqT6oMd8I5v6BMJ0xd96MkGf4N2/bDj209eACeIB5HZi234cY4zPs0d3lq5LpeWlpbJXl0EgVwHeauwsPBNm0EgQK/gqwuapl2VSqUG5/g/Wy+sERplCKFuJyennuzs7NWBgYEtbW1twXl5eW9rtVov6OMDmi09RyKRREOj1MfH5wJA+BalBue+vr5ncT2kCMFXVSgUeBqIwcHBPs6+Mjw0NNRhjH88PS0CX5Samvpn1iD2EjZUxqhGSCtFBe+8fqRxrxY5dQ/kK+A/ACJ85SJmgLx+pEqlqhAD8vthl8NeESqCbQZpeDBB4MltMd6QPQ+CYx4XkC/YrbI2KadW0Abog63R4yGDTsxMXtMAGGQNbCtFAA9ZehIl2WwJGWMPHG3sgTMjWXA64IlJNr67POHBfxAGDXoPG59soJAKFAQpiazDxrTrJTL364Kg3Ft93mrAJnHuUoQ8elwQVEcn7neWe9dbuhg5rAydfjAifm2ZIBg6bdkl76DY4xDxmp0TMg//SzHJWwucXRV3rK2OAXdl2EmICK9xPdFNGVoukbq3Gq8/rC0rvW6oBV7NbPvTDd5rsgaNulx7fQWCaTFpwy7GaPt7SLsLGSzLmIK9kRsWhoJpYuyCqoh5/e5+E/9BO7nchFcewlPkqYpoEnPoIDb4G8/GprfYtTf4036UuS04avaUvrKl6L9oe/zZQeSafESUWfmGIqEiSQSUJPSq5bZgtq3c2hgFYWuQxQ0SSCIeMxZctnuDxEuJprf4SS4Qfn4HoKdkeM+BfBe+dRLJnAXJpklEvmdgDEoHlD1OkfQB0kFoZIenKP8xjRHhff5/YCGTriMHi0PJxdmtlcrL3XOnub1BDVFEv7NE2m0SCs5HnX7YlY0wtK4yj2u4XuHue/XlJzfuwk6OGyNGbxfY1HOvU5b61pMbrnc2PwZv9d2Z3O9+xzbhc7Te1tcMRCRM7Ir8+L/HZaub+e1iPofCXz+2JkvMQhZKStNVxYNbyL+OesjmE/jttKmi8l9dSbZgbjuUzZy83M7bmbWj+EhvhweJNWLjJI7BNuVj1khgkN21Ex0oE//XaEPj2IOHMTvAWJfVvwUYAKkVWW9BNMncAAAAAElFTkSuQmCC);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.success:after,
|
||||
.error:after,
|
||||
.info:after,
|
||||
.warning:after,
|
||||
.edit:after,
|
||||
.lock:after,
|
||||
.tip:after,
|
||||
.download:after,
|
||||
.chat:after,
|
||||
.task:after
|
||||
{
|
||||
content: "x";
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 5px;
|
||||
height: 6px;
|
||||
cursor: pointer;
|
||||
font: normal normal 13px/20px "Lucida Grande", "Lucida Sans Unicode", sans-serif;
|
||||
font-size: 13px;
|
||||
-webkit-text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
|
||||
-moz-text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
|
||||
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
|
||||
/*
|
||||
Awesome trick !
|
||||
http://stackoverflow.com/questions/7478336/only-detect-click-event-on-pseudo-element
|
||||
http://jsfiddle.net/ZWw3Z/70/
|
||||
*/
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.success
|
||||
{
|
||||
border: 1px solid #accc5d;
|
||||
color: #70892b;
|
||||
background-color: #c8e185;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #d0e98e), color-stop(100%, #c1da7f));
|
||||
background-image: -webkit-linear-gradient( #d0e98e, #c1da7f );
|
||||
background-image: -moz-linear-gradient( #d0e98e, #c1da7f );
|
||||
background-image: -o-linear-gradient( #d0e98e, #c1da7f );
|
||||
background-image: linear-gradient( #d0e98e, #c1da7f );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.3 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.3 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.3 );
|
||||
}
|
||||
|
||||
.success:before
|
||||
{
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
|
||||
@-webkit-keyframes error
|
||||
{
|
||||
0% { -webkit-transform: translateX( 0px ); }
|
||||
25% { -webkit-transform: translateX( 30px ); }
|
||||
45% { -webkit-transform: translateX( -30px ); }
|
||||
65% { -webkit-transform: translateX( 30px ); }
|
||||
82% { -webkit-transform: translateX( -30px ); }
|
||||
94% { -webkit-transform: translateX( 30px ); }
|
||||
35%, 55%, 75%, 87%, 97%, 100% { -webkit-transform: translateX( 0px ); }
|
||||
}
|
||||
|
||||
.error
|
||||
{
|
||||
border: 1px solid #dc4e4d;
|
||||
color: #b52525;
|
||||
background-color: #ec8282;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f48888), color-stop(100%, #e17575));
|
||||
background-image: -webkit-linear-gradient( #f48888, #e17575 );
|
||||
background-image: -moz-linear-gradient( #f48888, #e17575 );
|
||||
background-image: -o-linear-gradient( #f48888, #e17575 );
|
||||
background-image: linear-gradient( #f48888, #e17575 );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-webkit-animation: error 0.35s linear;
|
||||
-moz-animation: error 0.35s linear;
|
||||
}
|
||||
|
||||
.error-shake
|
||||
{
|
||||
-webkit-animation: error 0.35s linear;
|
||||
-moz-animation: error 0.35s linear;
|
||||
animation: error 0.35s linear;
|
||||
}
|
||||
|
||||
.error:before
|
||||
{
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.info
|
||||
{
|
||||
border: 1px solid #69c0ca;
|
||||
color: #3d8d98;
|
||||
background-color: #8aced6;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #99e2eb), color-stop(100%, #79c6cd));
|
||||
background-image: -webkit-linear-gradient( #99e2eb, #79c6cd );
|
||||
background-image: -moz-linear-gradient( #99e2eb, #79c6cd );
|
||||
background-image: -o-linear-gradient( #99e2eb, #79c6cd );
|
||||
background-image: linear-gradient( #99e2eb, #79c6cd );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
}
|
||||
|
||||
.info:before
|
||||
{
|
||||
background-position: 0 -30px;
|
||||
}
|
||||
|
||||
.warning
|
||||
{
|
||||
color: #c2721b;
|
||||
border: 1px solid #f9b516;
|
||||
background-color: #fbb160;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffd57f), color-stop(100%, #ffa544));
|
||||
background-image: -webkit-linear-gradient( #ffd57f, #ffa544 );
|
||||
background-image: -moz-linear-gradient( #ffd57f, #ffa544 );
|
||||
background-image: -o-linear-gradient( #ffd57f, #ffa544 );
|
||||
background-image: linear-gradient( #ffd57f, #ffa544 );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
}
|
||||
|
||||
.warning:before
|
||||
{
|
||||
background-position: 0 -45px;
|
||||
}
|
||||
|
||||
.edit
|
||||
{
|
||||
color: #ae8500;
|
||||
border: 1px solid #e9c95f;
|
||||
background-color: #f3dc8f;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffeaa7), color-stop(100%, #f3d573));
|
||||
background-image: -webkit-linear-gradient( #ffeaa7, #f3d573 );
|
||||
background-image: -moz-linear-gradient( #ffeaa7, #f3d573 );
|
||||
background-image: -o-linear-gradient( #ffeaa7, #f3d573 );
|
||||
background-image: linear-gradient( #ffeaa7, #f3d573 );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
}
|
||||
|
||||
.edit:before
|
||||
{
|
||||
background-position: 0 -60px;
|
||||
}
|
||||
|
||||
.lock
|
||||
{
|
||||
border: 1px solid #CCC;
|
||||
color: #666;
|
||||
background-color: #e8e8e8;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f4f4f4), color-stop(100%, #e0e0e0));
|
||||
background-image: -webkit-linear-gradient( #f4f4f4, #e0e0e0 );
|
||||
background-image: -moz-linear-gradient( #f4f4f4, #e0e0e0 );
|
||||
background-image: -o-linear-gradient( #f4f4f4, #e0e0e0 );
|
||||
background-image: linear-gradient( #f4f4f4, #e0e0e0 );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
}
|
||||
|
||||
.lock:before
|
||||
{
|
||||
background-position: 0 -75px;
|
||||
}
|
||||
|
||||
.tip
|
||||
{
|
||||
border: 1px solid #e6b96f;
|
||||
color: #b1802f;
|
||||
background-color: #f5dcb2;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffedcf), color-stop(100%, #f8d69e));
|
||||
background-image: -webkit-linear-gradient( #ffedcf, #f8d69e );
|
||||
background-image: -moz-linear-gradient( #ffedcf, #f8d69e );
|
||||
background-image: -o-linear-gradient( #ffedcf, #f8d69e );
|
||||
background-image: linear-gradient( #ffedcf, #f8d69e );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
}
|
||||
|
||||
.tip:before
|
||||
{
|
||||
background-position: 0 -90px;
|
||||
}
|
||||
|
||||
.download
|
||||
{
|
||||
border: 1px solid #3178c0;
|
||||
color: #0c4fa3;
|
||||
background-color: #6dacea;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #8ed0fa), color-stop(100%, #4e95dc));
|
||||
background-image: -webkit-linear-gradient( #8ed0fa, #4e95dc );
|
||||
background-image: -moz-linear-gradient( #8ed0fa, #4e95dc );
|
||||
background-image: -o-linear-gradient( #8ed0fa, #4e95dc );
|
||||
background-image: linear-gradient( #8ed0fa, #4e95dc );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
}
|
||||
|
||||
.download:before
|
||||
{
|
||||
background-position: 0 -105px;
|
||||
}
|
||||
|
||||
.chat
|
||||
{
|
||||
color: #366f11;
|
||||
border: 1px solid #5d902f;
|
||||
background-color: #89bc5a;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #8dcb3d), color-stop(100%, #74a547));
|
||||
background-image: -webkit-linear-gradient( #8dcb3d, #74a547 );
|
||||
background-image: -moz-linear-gradient( #8dcb3d, #74a547 );
|
||||
background-image: -o-linear-gradient( #8dcb3d, #74a547 );
|
||||
background-image: linear-gradient( #8dcb3d, #74a547 );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
}
|
||||
|
||||
.chat:before
|
||||
{
|
||||
background-position: 0 -120px;
|
||||
}
|
||||
|
||||
.task
|
||||
{
|
||||
color: #432c12;
|
||||
border: 1px solid #71502b;
|
||||
background-color: #92724e;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a58868), color-stop(100%, #886640));
|
||||
background-image: -webkit-linear-gradient( #a58868, #886640 );
|
||||
background-image: -moz-linear-gradient( #a58868, #886640 );
|
||||
background-image: -o-linear-gradient( #a58868, #886640 );
|
||||
background-image: linear-gradient( #a58868, #886640 );
|
||||
-webkit-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
-moz-text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
text-shadow: 0px 1px rgba( 255, 255, 255, 0.2 );
|
||||
}
|
||||
|
||||
.task:before
|
||||
{
|
||||
background-position: 0 -135px;
|
||||
}
|
||||
|
||||
#autoconfig_save:hover,
|
||||
#autoconfig_remove:hover,
|
||||
#autoconfig_cancel:hover
|
||||
{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#autoconfig_provider_domain option:disabled:hover,
|
||||
#autoconfig_remove:disabled
|
||||
{
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* If this move up button is in the first host block, then deactivate it */
|
||||
.autoconfig-incoming:first-child .autoconfig-move-up,
|
||||
.autoconfig-incoming:last-child .autoconfig-move-down,
|
||||
.autoconfig-outgoing:first-child .autoconfig-move-up,
|
||||
.autoconfig-outgoing:last-child .autoconfig-move-down
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#autoconfig_form > table
|
||||
{
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.autoconfig-incoming,
|
||||
.autoconfig-outgoing,
|
||||
.autoconfig-instruction,
|
||||
.autoconfig-documentation
|
||||
{
|
||||
border-bottom: 1pt solid black;
|
||||
}
|
||||
|
||||
/* CSS switch designed and credit to Thibaut Courouble http://thibaut.me */
|
||||
.switch
|
||||
{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 56px;
|
||||
height: 20px;
|
||||
padding: 3px;
|
||||
vertical-align: top;
|
||||
background-color: white;
|
||||
border-radius: 18px;
|
||||
box-shadow: inset 0 -1px white, inset 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
cursor: pointer;
|
||||
background-image: -webkit-linear-gradient(top, #eeeeee, white 25px);
|
||||
background-image: -moz-linear-gradient(top, #eeeeee, white 25px);
|
||||
background-image: -o-linear-gradient(top, #eeeeee, white 25px);
|
||||
background-image: linear-gradient(to bottom, #eeeeee, white 25px);
|
||||
}
|
||||
|
||||
.switch-input
|
||||
{
|
||||
/*
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
*/
|
||||
display: none;
|
||||
}
|
||||
|
||||
.switch-label
|
||||
{
|
||||
position: relative;
|
||||
display: block;
|
||||
height: inherit;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
background: #eceeef;
|
||||
border-radius: inherit;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.15);
|
||||
-webkit-transition: 0.15s ease-out;
|
||||
-moz-transition: 0.15s ease-out;
|
||||
-o-transition: 0.15s ease-out;
|
||||
transition: 0.15s ease-out;
|
||||
-webkit-transition-property: opacity background;
|
||||
-moz-transition-property: opacity background;
|
||||
-o-transition-property: opacity background;
|
||||
transition-property: opacity background;
|
||||
}
|
||||
|
||||
.switch-label:before,
|
||||
.switch-label:after
|
||||
{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -.5em;
|
||||
line-height: 1;
|
||||
-webkit-transition: inherit;
|
||||
-moz-transition: inherit;
|
||||
-o-transition: inherit;
|
||||
transition: inherit;
|
||||
}
|
||||
|
||||
.switch-label:before
|
||||
{
|
||||
content: attr(data-off);
|
||||
right: 11px;
|
||||
color: #aaa;
|
||||
text-shadow: 0 1px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.switch-label:after
|
||||
{
|
||||
content: attr(data-on);
|
||||
left: 11px;
|
||||
color: white;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.2);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
input[name="enable_status"].switch-input:checked ~ table [for="enable_status"].switch > .switch-label,
|
||||
input[name="documentation_status"].switch-input:checked ~ table [for="documentation_status"].switch > .switch-label
|
||||
{
|
||||
background: #47a8d8;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15), inset 0 0 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
input[name="enable_status"].switch-input:checked ~ table [for="enable_status"].switch > .switch-label:before,
|
||||
input[name="documentation_status"].switch-input:checked ~ table [for="documentation_status"].switch > .switch-label:before
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
input[name="enable_status"].switch-input:checked ~ table [for="enable_status"].switch > .switch-label:after,
|
||||
input[name="documentation_status"].switch-input:checked ~ table [for="documentation_status"].switch > .switch-label:after
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.switch-handle
|
||||
{
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
|
||||
background-image: -webkit-linear-gradient(top, white 40%, #f0f0f0);
|
||||
background-image: -moz-linear-gradient(top, white 40%, #f0f0f0);
|
||||
background-image: -o-linear-gradient(top, white 40%, #f0f0f0);
|
||||
background-image: linear-gradient(to bottom, white 40%, #f0f0f0);
|
||||
-webkit-transition: left 0.15s ease-out;
|
||||
-moz-transition: left 0.15s ease-out;
|
||||
-o-transition: left 0.15s ease-out;
|
||||
transition: left 0.15s ease-out;
|
||||
}
|
||||
|
||||
.switch-handle:before
|
||||
{
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -6px 0 0 -6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 1px rgba(0, 0, 0, 0.02);
|
||||
background-image: -webkit-linear-gradient(top, #eeeeee, white);
|
||||
background-image: -moz-linear-gradient(top, #eeeeee, white);
|
||||
background-image: -o-linear-gradient(top, #eeeeee, white);
|
||||
background-image: linear-gradient(to bottom, #eeeeee, white);
|
||||
}
|
||||
|
||||
input[name="enable_status"].switch-input:checked ~ table [for="enable_status"].switch > .switch-handle,
|
||||
input[name="documentation_status"].switch-input:checked ~ table [for="documentation_status"].switch > .switch-handle
|
||||
{
|
||||
left: 40px;
|
||||
box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
input[name="enable_status"] ~ table tr.autoconfig-instruction,
|
||||
input[name="documentation_status"] ~ table tr.autoconfig-documentation
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[name="enable_status"]:checked ~ table tr.autoconfig-instruction,
|
||||
input[name="documentation_status"]:checked ~ table tr.autoconfig-documentation
|
||||
{
|
||||
display: table-row;
|
||||
}
|
||||
|
@ -0,0 +1,910 @@
|
||||
/*
|
||||
Created on 2020-03-12
|
||||
Copyright 2020 Jacques Deguest
|
||||
Distributed under the same licence as Postfix Admin
|
||||
*/
|
||||
$(document).ready(function()
|
||||
{
|
||||
const DEBUG = false;
|
||||
|
||||
// Credits to: https://tdanemar.wordpress.com/2010/08/24/jquery-serialize-method-and-checkboxes/
|
||||
// Modified by Jacques Deguest to include other form elements:
|
||||
// http://www.w3schools.com/tags/tag_input.asp
|
||||
(function($)
|
||||
{
|
||||
$.fn.serializeAll = function(options)
|
||||
{
|
||||
return $.param(this.serializeArrayAll(options));
|
||||
};
|
||||
|
||||
$.fn.serializeArrayAll = function (options)
|
||||
{
|
||||
var o = $.extend({
|
||||
checkboxesAsBools: false
|
||||
}, options || {});
|
||||
|
||||
var rselectTextarea = /select|textarea/i;
|
||||
var rinput = /text|hidden|password|search|date|time|number|color|datetime|email|file|image|month|range|tel|url|week/i;
|
||||
|
||||
return this.map(function ()
|
||||
{
|
||||
return this.elements ? $.makeArray(this.elements) : this;
|
||||
})
|
||||
.filter(function ()
|
||||
{
|
||||
return this.name && !this.disabled &&
|
||||
(this.checked
|
||||
|| (o.checkboxesAsBools && this.type === 'checkbox')
|
||||
|| rselectTextarea.test(this.nodeName)
|
||||
|| rinput.test(this.type));
|
||||
})
|
||||
.map(function (i, elem)
|
||||
{
|
||||
var val = $(this).val();
|
||||
return val == null ?
|
||||
null :
|
||||
$.isArray(val) ?
|
||||
$.map(val, function (val, i)
|
||||
{
|
||||
return { name: elem.name, value: val };
|
||||
}) :
|
||||
{
|
||||
name: elem.name,
|
||||
value: (o.checkboxesAsBools && this.type === 'checkbox') ?
|
||||
(this.checked ? 1 : 0) :
|
||||
val
|
||||
};
|
||||
}).get();
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
window.makeMessage = function( type, mesg )
|
||||
{
|
||||
return( sprintf( '<div class="%s">%s</div>', type, mesg ) );
|
||||
};
|
||||
|
||||
window.showMessage = function()
|
||||
{
|
||||
if( DEBUG ) console.log( "Called from " + ( arguments.callee.caller === null ? 'void' : arguments.callee.caller.name ) );
|
||||
var opts = {
|
||||
div: $('#message'),
|
||||
append: false,
|
||||
dom: null,
|
||||
timeout: null,
|
||||
scroll: false,
|
||||
timeoutCallback: null,
|
||||
};
|
||||
var msgDiv = $('#message');
|
||||
if( arguments.length == 3 && typeof( arguments[2] ) === 'object' )
|
||||
{
|
||||
var param = arguments[2];
|
||||
opts.type = arguments[0];
|
||||
opts.message = arguments[1];
|
||||
if( typeof( param.append ) !== 'undefined' ) opts.append = param.append;
|
||||
if( typeof( param.dom ) !== 'undefined' ) opts.dom = param.dom;
|
||||
if( typeof( param.timeout ) !== 'undefined' ) opts.timeout = param.timeout;
|
||||
if( typeof( param.timeoutCallback ) !== 'undefined' ) opts.timeoutCallback = param.timeoutCallback;
|
||||
if( typeof( param.scroll ) !== 'undefined' ) opts.scroll = param.scroll;
|
||||
if( typeof( param.speak ) !== 'undefined' ) opts.speak = param.speak;
|
||||
}
|
||||
// Backward compatibility
|
||||
else if( arguments.length >= 2 )
|
||||
{
|
||||
opts.type = arguments[0];
|
||||
opts.message = arguments[1];
|
||||
if( arguments.length > 2 ) opts.append = arguments[2];
|
||||
if( arguments.length > 3 ) opts.dom = arguments[3];
|
||||
if( arguments.length > 4 ) opts.timeout = arguments[4];
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDiv.append( makeMessage( 'warning', "showMessage called with only " + arguments.length + " parameters while 2 at minimum are required. Usage: showMessage( type, message, append, domObject, timeout ) or showMessage( type, message, options )" ) );
|
||||
return( false );
|
||||
}
|
||||
if( typeof( opts.dom ) === 'object' && opts.dom != null )
|
||||
{
|
||||
msgDiv = opts.dom;
|
||||
}
|
||||
// Check if message is an array of messages
|
||||
// https://stackoverflow.com/a/4775741/4814971
|
||||
if( Array.isArray )
|
||||
{
|
||||
if( Array.isArray( opts.message ) )
|
||||
{
|
||||
opts.messages = opts.message;
|
||||
}
|
||||
}
|
||||
else if( opts.message instanceof( Array ) )
|
||||
{
|
||||
opts.messages = opts.message;
|
||||
}
|
||||
else if( $.isArray( opts.message ) )
|
||||
{
|
||||
opts.messages = opts.message;
|
||||
}
|
||||
|
||||
// messages has been set with previous check
|
||||
// Make this array a list of errors
|
||||
if( opts.hasOwnProperty( 'messages' ) )
|
||||
{
|
||||
opts.message = sprintf( "<ol>\n%s\n</ol>", opts.messages.map(function(e){ return('<li>' + e + '</li>'); }).join( "\n" ) );
|
||||
}
|
||||
|
||||
if( opts.append )
|
||||
{
|
||||
msgDiv.append(makeMessage(opts.type, opts.message));
|
||||
}
|
||||
else
|
||||
{
|
||||
msgDiv.html(makeMessage(opts.type, opts.message));
|
||||
}
|
||||
|
||||
if( opts.type == 'error' )
|
||||
{
|
||||
msgDiv.addClass( 'error-shake' );
|
||||
setTimeout(function()
|
||||
{
|
||||
msgDiv.removeClass( 'error-shake' );
|
||||
}, 70000);
|
||||
}
|
||||
|
||||
if( parseInt( opts.timeout ) > 0 )
|
||||
{
|
||||
var thisTimeout = parseInt( opts.timeout );
|
||||
setTimeout(function()
|
||||
{
|
||||
msgDiv.html( '' );
|
||||
if( typeof( opts.timeoutCallback ) === 'function' )
|
||||
{
|
||||
opts.timeoutCallback();
|
||||
}
|
||||
},thisTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
setTimeout(function()
|
||||
{
|
||||
msgDiv.html( '' );
|
||||
},15000);
|
||||
}
|
||||
if( opts.scroll )
|
||||
{
|
||||
if( DEBUG ) console.log( "Scrolling to the top of the page..." );
|
||||
$('html, body').animate( { scrollTop: 0 }, 500 );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.log( "No scrolling..." );
|
||||
}
|
||||
};
|
||||
|
||||
window.postfixAdminProgressBar = function()
|
||||
{
|
||||
var xhr = $.ajaxSettings.xhr();
|
||||
if( DEBUG ) console.log( "Initiating the progress bar." );
|
||||
if( DEBUG ) console.log( "Called from:\n" + (new Error).stack );
|
||||
$('#postfixadmin-progress').css( 'width', '0%' ).show().removeClass('done');
|
||||
xhr.upload.onprogress = function(evt)
|
||||
{
|
||||
if( evt.lengthComputable )
|
||||
{
|
||||
var percentComplete = evt.loaded / evt.total;
|
||||
if( DEBUG ) console.log(percentComplete);
|
||||
$('#postfixadmin-progress').css({
|
||||
width: percentComplete * 100 + '%' });
|
||||
if( DEBUG ) console.log( "upload.addEventListener: " + percentComplete );
|
||||
if( percentComplete === 1 )
|
||||
{
|
||||
// $('#postfixadmin-progress').addClass('done').hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.onprogress = function(evt)
|
||||
{
|
||||
if( evt.lengthComputable )
|
||||
{
|
||||
var percentComplete = evt.loaded / evt.total;
|
||||
if( DEBUG ) console.log("addEventListener: " + percentComplete);
|
||||
$('#postfixadmin-progress').css({
|
||||
width: percentComplete * 100 + '%' });
|
||||
if( percentComplete === 1 )
|
||||
{
|
||||
// $('#postfixadmin-progress').addClass('done').hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
return( xhr );
|
||||
};
|
||||
|
||||
window.postfixAdminProgressBarStart = function()
|
||||
{
|
||||
$({property: 0}).animate({property: 85},
|
||||
{
|
||||
// Arbitrary time, which should well cover the time it takes to get response from server
|
||||
// Otherwise, well our progress bar will hang at 85% until we get a call to kill it
|
||||
duration: 4000,
|
||||
step: function()
|
||||
{
|
||||
var _percent = Math.round( this.property );
|
||||
$('#postfixadmin-progress').css( 'width', _percent + '%' );
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.postfixAdminProgressBarStop = function()
|
||||
{
|
||||
$({property: 85}).animate({property: 105},
|
||||
{
|
||||
duration: 1000,
|
||||
step: function()
|
||||
{
|
||||
var _percent = Math.round( this.property );
|
||||
$('#postfixadmin-progress').css( 'width', _percent + '%' );
|
||||
if( _percent == 105 )
|
||||
{
|
||||
$('#postfixadmin-progress').addClass('done');
|
||||
}
|
||||
},
|
||||
complete: function()
|
||||
{
|
||||
$('#postfixadmin-progress').hide();
|
||||
$('#postfixadmin-progress').css( 'width', '0%' );
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.autoconfigShowHideArrow = function()
|
||||
{
|
||||
// Reset. Show them all
|
||||
$('table.server .autoconfig-command').show();
|
||||
// Hide first and last ones
|
||||
// This requires jQUery up to v3.3. Version 3.4 onward do not support :first and :last anymore
|
||||
$('.autoconfig-incoming:first .autoconfig-move-up').hide();
|
||||
$('.autoconfig-incoming:last .autoconfig-move-down').hide();
|
||||
$('.autoconfig-outgoing:first .autoconfig-move-up').hide();
|
||||
$('.autoconfig-outgoing:last .autoconfig-move-down').hide();
|
||||
};
|
||||
|
||||
window.autoconfig_ajax_call = function( postData )
|
||||
{
|
||||
var prom = $.Deferred();
|
||||
$this = $(this);
|
||||
$.ajax({
|
||||
xhr: postfixAdminProgressBar,
|
||||
type: "POST",
|
||||
url: "autoconfig.php",
|
||||
dataType: "json",
|
||||
data: postData,
|
||||
beforeSend: function(xhr)
|
||||
{
|
||||
xhr.overrideMimeType( "application/json; charset=utf-8" );
|
||||
if( DEBUG ) console.log( "Initiating the progress bar." );
|
||||
if( DEBUG ) console.log( "Called from:\n" + (new Error).stack );
|
||||
// $('#postfixadmin-progress').show().removeClass('done');
|
||||
postfixAdminProgressBarStart();
|
||||
}
|
||||
}).fail(function(xhr, errType, ExceptionObject)
|
||||
{
|
||||
postfixAdminProgressBarStop();
|
||||
if( DEBUG ) console.log( "Returned error " + xhr.status + " with error type " + errType + " and exception object " + JSON.stringify( ExceptionObject ) );
|
||||
if( DEBUG ) console.log( "Current url is: " + xhr.responseURL );
|
||||
if( DEBUG ) console.log( "Http headers are: " + xhr.getAllResponseHeaders() );
|
||||
if( DEBUG ) console.log( "Response raw data is: \n" + xhr.responseText );
|
||||
// There was a redirect most likely due to some timeout
|
||||
if( typeof( xhr ) !== 'undefined' )
|
||||
{
|
||||
if( DEBUG ) console.log( "Xhr is: " + JSON.stringify( xhr ) );
|
||||
if( xhr.getResponseHeader( 'Content-Type' ).toLowerCase().indexOf( 'text/html' ) >= 0 )
|
||||
{
|
||||
window.location.reload();
|
||||
return( true );
|
||||
}
|
||||
}
|
||||
var msg = 'An unexpected error has occurred';
|
||||
showMessage( 'error', msg, { scroll: true });
|
||||
$this.addClass( 'error-shake' );
|
||||
prom.reject();
|
||||
}).done(function(data, status, xhr)
|
||||
{
|
||||
postfixAdminProgressBarStop();
|
||||
if( data.error )
|
||||
{
|
||||
showMessage( 'error', data.error, { scroll: true });
|
||||
$this.addClass( 'error-shake' );
|
||||
setTimeout(function()
|
||||
{
|
||||
$this.removeClass( 'error-shake' );
|
||||
},5000);
|
||||
prom.reject();
|
||||
}
|
||||
else
|
||||
{
|
||||
if( data.success )
|
||||
{
|
||||
prom.resolve(data);
|
||||
showMessage( 'success', data.success, { scroll: true });
|
||||
if( DEBUG ) console.log( "save(): " + data.success );
|
||||
}
|
||||
else if( data.info )
|
||||
{
|
||||
showMessage( 'info', data.info, { scroll: true } );
|
||||
prom.resolve();
|
||||
}
|
||||
else
|
||||
{
|
||||
showMessage( 'info', data.msg, { scroll: true } );
|
||||
prom.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
return( prom.promise() );
|
||||
};
|
||||
|
||||
$(document).on('click','#autoconfig_save', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
$this = $(this);
|
||||
var data = {handler: 'autoconfig_save'};
|
||||
$('#autoconfig_form').serializeArrayAll().map(function(item)
|
||||
{
|
||||
if( data[ item.name ] !== undefined )
|
||||
{
|
||||
if( !data[ item.name ].push )
|
||||
{
|
||||
data[ item.name ] = [ data[item.name] ];
|
||||
}
|
||||
data[ item.name ].push( item.value );
|
||||
}
|
||||
else
|
||||
{
|
||||
data[ item.name ] = item.value;
|
||||
}
|
||||
});
|
||||
if( DEBUG ) console.log( "serialized data is: " + JSON.stringify( data ) );
|
||||
autoconfig_ajax_call( data ).done(function(data)
|
||||
{
|
||||
// Since this shared function is used for both saving (adding and updating) as well as deleting
|
||||
// we check if those data properties are returned by the server.
|
||||
// Those here are only returned if this is the result of an addition
|
||||
if( data.hasOwnProperty('config_id') )
|
||||
{
|
||||
if( $('#autoconfig_form input[name="config_id"]').length == 0 )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find the field \"config_id\" !" );
|
||||
}
|
||||
if( typeof( data.config_id ) !== 'undefined' && data.config_id.length > 0 )
|
||||
{
|
||||
// We force trigger change, because this is a hidden field and it hidden field do not trigger change
|
||||
$('#autoconfig_form input[name="config_id"]').val( data.config_id ).trigger('change');
|
||||
}
|
||||
}
|
||||
// Do the hosts
|
||||
var hostTypes = [ 'incoming', 'outgoing' ];
|
||||
for( var j = 0; j < hostTypes.length; j++ )
|
||||
{
|
||||
var hostType = hostTypes[j];
|
||||
if( DEBUG ) console.log( "Checking host of type " + hostType );
|
||||
if( data.hasOwnProperty(hostType + '_server') && Array.isArray( data[hostType + '_server'] ) )
|
||||
{
|
||||
var thoseHosts = $('.autoconfig-' + hostType);
|
||||
var dataHosts = data[hostType + '_server'];
|
||||
if( thoseHosts.length == 0 )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find any hosts block in our form!" );
|
||||
}
|
||||
else if( dataHosts.length != thoseHosts.length )
|
||||
{
|
||||
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Total of " + hostType + " servers returned from the server (" + dataHosts.length + ") do not match the total hosts we have in our form (" + thoseHosts.length + ")." );
|
||||
}
|
||||
dataHosts.forEach(function(def, index)
|
||||
{
|
||||
if( DEBUG ) console.log( "def contains: " + JSON.stringify( def ) );
|
||||
if( !def.hasOwnProperty('config_id') ||
|
||||
!def.hasOwnProperty('hostname') ||
|
||||
!def.hasOwnProperty('port') )
|
||||
{
|
||||
if( DEBUG ) console.error( "Something is wrong. Data received is missing fields config_id, or hostname or port" );
|
||||
return( false );
|
||||
}
|
||||
thoseHosts.each(function(offset, obj)
|
||||
{
|
||||
var dom = $(obj);
|
||||
var hostId = dom.find('input[name="host_id[]"]');
|
||||
var hostName = dom.find('input[name="hostname[]"]');
|
||||
var hostPort = dom.find('input[name="port[]"]');
|
||||
if( !hostId.length )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find host id field for host type \"" + hostType + "\" at offset " + i );
|
||||
}
|
||||
if( !hostName.length )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find host name field for host type \"" + hostType + "\" at offset " + i );
|
||||
}
|
||||
if( !hostPort.length )
|
||||
{
|
||||
showMessage( 'error', 'An unexpected error has occurred (check web console for details)', { scroll: true });
|
||||
throw( "Unable to find host port field for host type \"" + hostType + "\" at offset " + i );
|
||||
}
|
||||
// We found our match: no id, hostname and port match
|
||||
if( hostId.val().length == 0 && hostName.val() == def.hostname && hostPort.val() == def.port )
|
||||
{
|
||||
if( DEBUG ) console.log( "Setting host id " + def.host_id + " to host name " + hostName.val() + " with port " + hostPort.val() );
|
||||
hostId.val( def.host_id );
|
||||
// exit the loop
|
||||
return( false );
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.error( "Something is wrong. Data received for hosts of type " + hostType + " does not exist or is not an array." );
|
||||
}
|
||||
}
|
||||
|
||||
// Now, do the texts
|
||||
var textTypes = ['instruction', 'documentation'];
|
||||
for( var j = 0; j < textTypes.length; j++ )
|
||||
{
|
||||
var textType = textTypes[j];
|
||||
if( DEBUG ) console.log( "Checking text of type " + textType );
|
||||
if( data.hasOwnProperty(textType) && Array.isArray( data[textType] ) )
|
||||
{
|
||||
var dataTexts = data[textType];
|
||||
var thoseTextIds = $('input[name="' + textType + '_id[]"]');
|
||||
var thoseTextLang = $('select[name="' + textType + '_lang[]"]');
|
||||
var thoseTextData = $('textarea[name="' + textType + '_text[]"]');
|
||||
// The array could very well be empty
|
||||
dataTexts.forEach(function(def, index)
|
||||
{
|
||||
if( !def.hasOwnProperty('id') ||
|
||||
!def.hasOwnProperty('type') ||
|
||||
!def.hasOwnProperty('lang') )
|
||||
{
|
||||
if( DEBUG ) console.error( "Something is wrong. Data received is missing fields id, or type or lang" );
|
||||
return( false );
|
||||
}
|
||||
for( var k = 0; k < thoseTextIds.length; k++ )
|
||||
{
|
||||
var textId = thoseTextIds.eq(k);
|
||||
var textLang = thoseTextLang.eq(k);
|
||||
// Found a match
|
||||
if( textId.val().length == 0 && textLang.val() == def.lang )
|
||||
{
|
||||
if( DEBUG ) console.log( "Setting text id " + def.id + " to text with type " + textType + " and language " + def.lang );
|
||||
textId.val( def.id );
|
||||
return( false );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.error( "Something is wrong. Data received for " + textType + " text does not exist or is not an array." );
|
||||
}
|
||||
}
|
||||
}).fail(function()
|
||||
{
|
||||
// Nothing for now
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click','#autoconfig_remove', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var data =
|
||||
{
|
||||
handler: 'autoconfig_remove',
|
||||
config_id: $('input[name="config_id"]').val(),
|
||||
token: $('input[name="token"]').val(),
|
||||
};
|
||||
if( DEBUG ) console.log( "Data to be sent is: " + JSON.stringify( data ) );
|
||||
autoconfig_ajax_call( data ).done(function(data)
|
||||
{
|
||||
// Reload the page, but without the query string at the end
|
||||
window.location.href = '/' + window.location.pathname;
|
||||
}).fail(function()
|
||||
{
|
||||
// Nothing for now
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#autoconfig_cancel', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
window.location.href = 'list.php?table=domain';
|
||||
return( true );
|
||||
});
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
|
||||
window.getRandomInt = function(min, max)
|
||||
{
|
||||
return( Math.floor( Math.random() * Math.floor((max - min) + min) ) );
|
||||
}
|
||||
/*
|
||||
I could have just created one function and called it
|
||||
*/
|
||||
// Add and remove hosts
|
||||
$(document).on('click','.autoconfig-server-add',function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('table.server').closest('tr');
|
||||
if( !row.length )
|
||||
{
|
||||
throw( "Unable to find the current enclosing row." );
|
||||
}
|
||||
var clone = row.clone();
|
||||
clone.find('select,input[type!="hidden"],textarea').each(function(i,item)
|
||||
{
|
||||
$(item).val( '' );
|
||||
});
|
||||
// We need to remove the host_id so it can be treated as a new host and not an update of an existing one
|
||||
clone.find('input[name="host_id[]"]').val( '' );
|
||||
// Set default value and trigger change, which will call an event handler that will hide/show the section on pop3
|
||||
clone.find('.host_type').val('imap').trigger('change');
|
||||
var optionLabels = ['leave_messages_on_server', 'download_on_biff', 'days_to_leave_messages_on_server', 'check_interval'];
|
||||
optionLabels.forEach(function(fieldName, index)
|
||||
{
|
||||
if( DEBUG ) console.log( "Checking field name " + fieldName );
|
||||
var thisField = clone.find('input[name="' + fieldName + '[]"]');
|
||||
if( thisField.length > 0 )
|
||||
{
|
||||
var forThisField = $('label[for="' + thisField.attr('id') + '"]', clone);
|
||||
if( forThisField.length > 0 )
|
||||
{
|
||||
if( DEBUG ) console.log( "Field is " + thisField.html() + "\nLabel field is: " + forThisField.html() );
|
||||
thisField.attr('id', 'autoconfig_' + fieldName + '_' + getRandomInt(100,1000));
|
||||
maxAttempt = 10;
|
||||
if( DEBUG ) console.log( "Checking if generated id exists: " + thisField.attr('id') );
|
||||
while( $('#' + thisField.attr('id'), clone).length > 0 && ++maxAttempt < 10 )
|
||||
{
|
||||
thisField.attr('id', 'autoconfig_' + fieldName + '_' + getRandomInt(100,1000));
|
||||
}
|
||||
forThisField.attr('for', thisField.attr('id'));
|
||||
if( DEBUG ) console.log( "Final generated id is: " + thisField.attr('id') );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.error( "Unable to find label element for field name." );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
clone.insertAfter( row );
|
||||
autoconfigShowHideArrow();
|
||||
$('html, body').animate( { scrollTop: clone.offset().top }, 500 );
|
||||
return( true );
|
||||
});
|
||||
|
||||
$(document).on('click','.autoconfig-server-remove',function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('table.server').closest('tr');
|
||||
if( !row.length )
|
||||
{
|
||||
throw( "Unable to find the current enclosing row." );
|
||||
}
|
||||
// Check if there are at least 2 elements, so that after at least one remain
|
||||
var re = new RegExp('(autoconfig-(?:incoming|outgoing))-server');
|
||||
var res = $(this).attr('class').match( re );
|
||||
console.log( res );
|
||||
if( res == null )
|
||||
{
|
||||
throw( "Cannot find class \"autoconfig-incoming-server\" or class \"autoconfig-outgoing-server\" in our clicked element." );
|
||||
}
|
||||
// Now find how many elements we have with this class
|
||||
var total = $('tr.' + res[1]).length;
|
||||
if( total < 2 )
|
||||
{
|
||||
row.addClass('autoconfig-error-shake');
|
||||
setTimeout(function()
|
||||
{
|
||||
row.removeClass('autoconfig-error-shake');
|
||||
},1000);
|
||||
return( false );
|
||||
}
|
||||
row.remove();
|
||||
autoconfigShowHideArrow();
|
||||
});
|
||||
|
||||
// Add and remove account enable instructions or support documentation
|
||||
$(document).on('click','.autoconfig-locale-text-add',function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('tr');
|
||||
if( !row.length )
|
||||
{
|
||||
throw( "Unable to find the current enclosing row." );
|
||||
}
|
||||
var clone = row.clone();
|
||||
clone.find('select,input[type!="hidden"],textarea').each(function(i,item)
|
||||
{
|
||||
$(item).val( '' );
|
||||
});
|
||||
// We need to remove the host_id so it can be treated as a new text and not an update of an existing one
|
||||
clone.find('input[name$="_id[]"]').val( '' );
|
||||
clone.insertAfter( row );
|
||||
$('html, body').animate( { scrollTop: clone.offset().top }, 500 );
|
||||
});
|
||||
|
||||
$(document).on('click','.autoconfig-locale-text-remove',function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('tr');
|
||||
if( !row.length )
|
||||
{
|
||||
throw( "Unable to find the current enclosing row." );
|
||||
}
|
||||
var re = new RegExp('(autoconfig-(instruction|documentation))');
|
||||
var res = $(this).attr('class').match( re );
|
||||
if( res == null )
|
||||
{
|
||||
throw( "Cannot find class \"autoconfig-instruction\" or class \"autoconfig-documentation\" in our clicked element." );
|
||||
}
|
||||
var textType = res[2];
|
||||
var total = $('tr.' + res[1]).length;
|
||||
if( DEBUG ) console.log( total + " rows found for text type " + textType );
|
||||
if( total < 2 )
|
||||
{
|
||||
if( DEBUG ) console.log( "text remove: one left" );
|
||||
if( DEBUG ) console.log( "Getting lang object with " + '[name="' + textType + '_lang[]"]' );
|
||||
var textLang = row.find('[name="' + textType + '_lang[]"]');
|
||||
if( DEBUG ) console.log( "Getting text object with " + '[name="' + textType + '_text[]"]' );
|
||||
var textData = row.find('[name="' + textType + '_text[]"]');
|
||||
if( DEBUG ) console.log( "text remove: found lang and text field? " + ( ( textLang.length > 0 && textData.length > 0 ) ? "Yes" : "No" ) );
|
||||
// This is remaining default tet row and there is no more data
|
||||
if( ( textLang.val() === null || ( textLang.val() !== null && textLang.val() == '' ) ) && $.trim(textData.val()) == '' )
|
||||
{
|
||||
if( DEBUG ) console.log( "text remove: lang and text fields are empty already, error shake it" );
|
||||
row.addClass('autoconfig-error-shake');
|
||||
setTimeout(function()
|
||||
{
|
||||
row.removeClass('autoconfig-error-shake');
|
||||
},1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) console.log( "text remove: empty fields" );
|
||||
textLang.val( '' );
|
||||
textData.val( '' );
|
||||
}
|
||||
return( false );
|
||||
}
|
||||
row.remove();
|
||||
});
|
||||
|
||||
$(document).on('click', '#copy_provider_value', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var orgField = $('input[name="organisation"]');
|
||||
var providerNameField = $('input[name="provider_name"]');
|
||||
if( !orgField.length || !providerNameField.length )
|
||||
{
|
||||
throw( "Unable to find either the provider name field or the organisation field!" );
|
||||
}
|
||||
if( providerNameField.val().length == 0 )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
orgField.val( providerNameField.val() );
|
||||
return( true );
|
||||
});
|
||||
|
||||
$(document).on('click', '#autoconfig_toggle_select_all_domains', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
// if( DEBUG ) console.log( "provider_domain options length: " + $('#autoconfig_provider_domain option').length + " and disabled options are: " + $('#autoconfig_provider_domain option:disabled').length + " and selected options are: " + $('#autoconfig_provider_domain option:selected').length );
|
||||
if( $('#autoconfig_provider_domain option:selected').length > 0 )
|
||||
{
|
||||
$('#autoconfig_provider_domain option').prop('selected', false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if( $('#autoconfig_provider_domain option:disabled').length == $('#autoconfig_provider_domain option').length )
|
||||
{
|
||||
var row = $(this).closest('tr');
|
||||
row.addClass('error-shake');
|
||||
setTimeout(function()
|
||||
{
|
||||
row.removeClass('error-shake');
|
||||
},500);
|
||||
return( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#autoconfig_provider_domain option:not(:disabled)').prop('selected', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('change', '#autoconfig_form .host_type', function(e)
|
||||
{
|
||||
var typeValue = $(this).val();
|
||||
// We get the enclosing table object to set some limiting context to the host_pop3 selector below
|
||||
var tbl = $(this).closest('table');
|
||||
if( typeValue == 'imap' )
|
||||
{
|
||||
$('.host_pop3', tbl).hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
$('.host_pop3', tbl).show();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('change', '#autoconfig_form .username_template', function(e)
|
||||
{
|
||||
var usernameField = $(this).closest('.server').find('input[name="username[]"]');
|
||||
if( usernameField.length == 0 )
|
||||
{
|
||||
throw( "Unable to find the username field!" );
|
||||
}
|
||||
usernameField.val( $(this).val() );
|
||||
});
|
||||
|
||||
$(document).on('change', '#autoconfig_form select[name="jump_to"]', function(e)
|
||||
{
|
||||
var id = $(this).val();
|
||||
var re = new RegExp( '([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})' );
|
||||
// This will display an empty form
|
||||
if( id.length == 0 )
|
||||
{
|
||||
window.location.href = 'autoconfig.php';
|
||||
}
|
||||
else if( id.match( re ) )
|
||||
{
|
||||
window.location.href = 'autoconfig.php?config_id=' + encodeURIComponent( id );
|
||||
}
|
||||
});
|
||||
|
||||
// Upon change in the content of the hidden config_id, enable or disable the "Remove" buttton
|
||||
// The Remove button can only be used if this is for an existing configuration obviously
|
||||
$(document).on('change', '#autoconfig_form input[name="config_id"]', function(e)
|
||||
{
|
||||
if( DEBUG ) console.log( "Config id field contains \"" + $(this).val() + "\"." );
|
||||
if( $(this).val().length == 0 || $(this).val().match( /^[[:blank:]\t]*$/ ) )
|
||||
{
|
||||
$('#autoconfig_remove').attr( 'disabled', true );
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#autoconfig_remove').attr( 'disabled', false );
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.autoconfig-move-up', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('.server').closest('tr');
|
||||
if( row.prev().length == 0 || ( !row.prev().hasClass('autoconfig-incoming') && !row.prev().hasClass('autoconfig-outgoing') ) )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
row.insertBefore( row.prev() );
|
||||
$('html, body').animate( { scrollTop: row.offset().top }, 500 );
|
||||
autoconfigShowHideArrow();
|
||||
});
|
||||
|
||||
$(document).on('click', '.autoconfig-move-down', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var row = $(this).closest('.server').closest('tr');
|
||||
if( row.next().length == 0 || ( !row.next().hasClass('autoconfig-incoming') && !row.next().hasClass('autoconfig-outgoing') ) )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
row.insertAfter( row.next() );
|
||||
$('html, body').animate( { scrollTop: row.offset().top }, 500 );
|
||||
autoconfigShowHideArrow();
|
||||
});
|
||||
|
||||
window.checkExistingTextLanguage = function(opts = {})
|
||||
{
|
||||
if( typeof( opts ) !== 'object' )
|
||||
{
|
||||
throw( "Parameters provided is not an object. Call checkExistingTextLanguage like this: checkExistingTextLanguage({ type: 'instruction', lang: 'fr', caller: $(this) })" );
|
||||
}
|
||||
var textType = opts.type;
|
||||
var callerMenu = opts.caller;
|
||||
var langToCheck = opts.lang;
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() checking text type " + textType + " for language " + langToCheck );
|
||||
if( typeof( textType ) === 'undefined' )
|
||||
{
|
||||
throw( "No text type was provided." );
|
||||
}
|
||||
var langMenu = $('select[name="' + textType + '_lang[]"]');
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() Found " + langMenu.length + " language menu(s)." );
|
||||
if( langMenu.length == 0 )
|
||||
{
|
||||
throw( "Could not find any language menu for this text type " + textType );
|
||||
}
|
||||
if( langMenu.length == 1 )
|
||||
{
|
||||
return( true );
|
||||
}
|
||||
var alreadySelected = false;
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() checking each existing language menu." );
|
||||
langMenu.each(function(offset, menuObject)
|
||||
{
|
||||
if( $(menuObject).is( callerMenu ) )
|
||||
{
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() skipping because this is our caller menu." );
|
||||
return( true );
|
||||
}
|
||||
// Found a match, stop there
|
||||
else if( $(menuObject).val() == langToCheck )
|
||||
{
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() Found match with this menu value (" + $(menuObject).val() + ") matching the language to check \"" + langToCheck + "\"." );
|
||||
alreadySelected = true;
|
||||
return( false );
|
||||
}
|
||||
});
|
||||
if( DEBUG ) console.log( "checkExistingTextLanguage() returning: " + !alreadySelected );
|
||||
return( !alreadySelected );
|
||||
};
|
||||
|
||||
// Upon selection or change of a language, we check it has not been selected already
|
||||
$(document).on('change', 'select[name="instruction_lang[]"]', function(e)
|
||||
{
|
||||
if( !checkExistingTextLanguage({ type: 'instruction', lang: $(this).val(), caller: $(this) }) )
|
||||
{
|
||||
$(this).addClass('error-shake');
|
||||
// <i class="fas fa-exclamation-triangle"></i>
|
||||
var warning = $('<i/>',
|
||||
{
|
||||
class: 'fas fa-exclamation-triangle fa-2x',
|
||||
style: 'color: red; font-size: 20px;',
|
||||
});
|
||||
warning.insertAfter( $(this) );
|
||||
var that = $(this);
|
||||
setTimeout(function()
|
||||
{
|
||||
that.removeClass('error-shake');
|
||||
warning.remove();
|
||||
},5000);
|
||||
$(this).val('');
|
||||
return( false );
|
||||
}
|
||||
return( true );
|
||||
});
|
||||
|
||||
window.toggleCertFiles = function(option)
|
||||
{
|
||||
if( typeof( option ) === 'undefined' )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
if( option == 'local' )
|
||||
{
|
||||
$('.cert_files').show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$('.cert_files').hide();
|
||||
}
|
||||
};
|
||||
|
||||
$(document).on('change', 'select[name="sign_option"]', function(e)
|
||||
{
|
||||
toggleCertFiles( $(this).val() );
|
||||
});
|
||||
|
||||
// Need to trigger the change for the host type menu
|
||||
if( $('#autoconfig_form .host_type').length > 0 )
|
||||
{
|
||||
$('#autoconfig_form .host_type').trigger('change');
|
||||
}
|
||||
if( $('#autoconfig_form input[name="config_id"]').length > 0 )
|
||||
{
|
||||
$('#autoconfig_form input[name="config_id"]').trigger('change');
|
||||
}
|
||||
// Hide useless up/down arrows
|
||||
autoconfigShowHideArrow();
|
||||
toggleCertFiles( $('select[name="sign_option"]').val() );
|
||||
});
|
@ -0,0 +1,307 @@
|
||||
<?php
|
||||
/**
|
||||
* Postfix Admin Auto Discovery Configuration
|
||||
*
|
||||
* LICENSE
|
||||
* This source file is subject to the GPL license that is bundled with
|
||||
* this package in the file LICENSE.TXT.
|
||||
*
|
||||
* Further details on the project are available at http://postfixadmin.sf.net
|
||||
*
|
||||
* @version $Id$
|
||||
* @license GNU GPL v2 or later.
|
||||
*
|
||||
* File: autoconfig.php
|
||||
*
|
||||
* Allows admin to configure Autodiscovery settings for mail domain names and
|
||||
* for Mac users or admins to generate the .mobile configuration file for Mac Mail.
|
||||
*
|
||||
* Template File: autoconfig.tpl, autoconfig-host-settings.tpl
|
||||
*
|
||||
* Template Variables:
|
||||
*
|
||||
* Form POST \ GET Variables:
|
||||
*/
|
||||
|
||||
require_once( 'common.php' );
|
||||
require_once( 'autoconfig_languages.php' );
|
||||
const DEBUG = false;
|
||||
|
||||
authentication_require_role('admin');
|
||||
$fUsername = authentication_get_username(); # enforce login
|
||||
$Return_url = "list.php?table=domain";
|
||||
mb_internal_encoding( 'UTF-8' );
|
||||
// $smarty->error_reporting = E_ALL & ~E_NOTICE;
|
||||
/*
|
||||
if( authentication_has_role('admin') )
|
||||
{
|
||||
$Admin_role = 1 ;
|
||||
$fDomain = safeget('domain');
|
||||
// $fUsername = safeget('username');
|
||||
// list(null $fDomain) = explode('@', $fUsername);
|
||||
$Return_url = "list-virtual.php?domain=" . urlencode( $fDomain );
|
||||
|
||||
if( $fDomain == '' || !check_owner( authentication_get_username(), $fDomain ) )
|
||||
{
|
||||
die( "Invalid username!" ); # TODO: better error message
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$Admin_role = 0 ;
|
||||
$Return_url = "main.php";
|
||||
authentication_require_role('user');
|
||||
}
|
||||
*/
|
||||
|
||||
// is autoconfig support enabled in $CONF ?
|
||||
if( $CONF['autoconfig'] == 'NO' || !array_key_exists( 'autoconfig', $CONF ) )
|
||||
{
|
||||
header( "Location: $Return_url" );
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
date_default_timezone_set( @date_default_timezone_get() ); # Suppress date.timezone warnings
|
||||
|
||||
$error = 0;
|
||||
|
||||
$fDomain = safeget('domain');
|
||||
$ah = new AutoconfigHandler( $fUsername );
|
||||
$ah->debug = DEBUG;
|
||||
$config_id = safeget('config_id');
|
||||
if( !empty( $fDomain ) && empty( $config_id ) )
|
||||
{
|
||||
$config_id = $ah->get_id_by_domain( $fDomain );
|
||||
}
|
||||
|
||||
// if( !$config_id )
|
||||
// {
|
||||
// flash_error( $PALANG['pAutoconfig_no_config_found'] );
|
||||
// $error = 1;
|
||||
// }
|
||||
$form = array();
|
||||
if( count( $ah->all_domains ) == 0 )
|
||||
{
|
||||
if( authentication_has_role( 'global-admin' ) )
|
||||
{
|
||||
flash_error( $PALANG['no_domains_exist'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
flash_error( $PALANG['no_domains_for_this_admin'] );
|
||||
}
|
||||
header( "Location: list.php?table=domain" ); # no domains (for this admin at least) - redirect to domain list
|
||||
exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
$form['provider_domain_options'] = $ah->all_domains;
|
||||
}
|
||||
|
||||
if( $_SERVER['REQUEST_METHOD'] == "GET" || empty( $_SERVER['REQUEST_METHOD'] ) )
|
||||
{
|
||||
if( DEBUG ) error_log( "config id submitted is: '$config_id'." );
|
||||
if( !empty( $config_id ) )
|
||||
{
|
||||
if( DEBUG ) error_log( "Getting configuration details with get_details()" );
|
||||
$form = $ah->get_details( $config_id );
|
||||
if( DEBUG ) error_log( "get_details() returned: " . print_r( $form, true ) );
|
||||
}
|
||||
if( empty( $form['account_type'] ) )
|
||||
{
|
||||
$form['account_type'] = 'imap';
|
||||
}
|
||||
if( empty( $form['ssl_enabled'] ) )
|
||||
{
|
||||
$form['ssl_enabled'] = 1;
|
||||
}
|
||||
if( empty( $form['active'] ) )
|
||||
{
|
||||
$form['active'] = 1;
|
||||
}
|
||||
$form['placeholder'] = array(
|
||||
'provider_id' => $ah->all_domains[0],
|
||||
'provider_name' => $PALANG['pAutoconfig_placeholder_provider_name'],
|
||||
);
|
||||
$form['config_options'] = $ah->get_config_ids();
|
||||
if( DEBUG ) error_log( "config_options is: " . print_r( $form['config_options'], true ) );
|
||||
// $config_id could be null
|
||||
$form['provider_domain_disabled'] = $ah->get_other_config_domains( $config_id );
|
||||
if( DEBUG ) error_log( "provider_domain_disabled is: " . print_r( $form['provider_domain_disabled'], true ) );
|
||||
// Get defaults
|
||||
if( count( $form['enable']['instruction'] ) == 0 )
|
||||
{
|
||||
$form['enable']['instruction'] = array(
|
||||
array( 'lang' => 'en', 'phrase' => '' )
|
||||
);
|
||||
if( strlen( $form['enable_status'] ) == 0 ) $form['enable_status'] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( strlen( $form['enable_status'] ) == 0 ) $form['enable_status'] = 1;
|
||||
}
|
||||
if( count( $form['documentation']['description'] ) == 0 )
|
||||
{
|
||||
$form['documentation']['description'] = array(
|
||||
array( 'lang' => 'en', 'phrase' => '' )
|
||||
);
|
||||
if( strlen( $form['documentation_status'] ) == 0 ) $form['documentation_status'] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( strlen( $form['documentation_status'] ) == 0 ) $form['documentation_status'] = 1;
|
||||
}
|
||||
showAutoconfigForm( $form );
|
||||
exit( 0 );
|
||||
}
|
||||
elseif( $_SERVER['REQUEST_METHOD'] == "POST" )
|
||||
{
|
||||
if( safepost('token') != $_SESSION['PFA_token'] )
|
||||
{
|
||||
die('Invalid token!');
|
||||
}
|
||||
if( !isset( $_SERVER[ 'HTTP_X_REQUESTED_WITH' ] ) ||
|
||||
strtolower( $_SERVER[ 'HTTP_X_REQUESTED_WITH' ] ) != 'xmlhttprequest' )
|
||||
{
|
||||
if( DEBUG ) error_log( "This request is not using Ajax." );
|
||||
flash_error( "Request is not using Ajax" );
|
||||
showAutoconfigForm( $_POST );
|
||||
exit( 0 );
|
||||
}
|
||||
if( isset( $_POST['config_id'] ) && !empty( $_POST['config_id'] ) )
|
||||
{
|
||||
if( DEBUG ) error_log( "Got config_id: " . $_POST['config_id'] );
|
||||
if( !$ah->config_id( $_POST['config_id'] ) )
|
||||
{
|
||||
json_reply( array( 'error' => sprintf( $PALANG['pAutoconfig_config_id_not_found'], $_POST['config_id'] ) ) );
|
||||
exit( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
$handler = null;
|
||||
if( isset( $_POST['handler'] ) )
|
||||
{
|
||||
if( preg_match( '/^[a-z][a-z_]+$/', $_POST['handler'] ) )
|
||||
{
|
||||
$handler = $_POST['handler'];
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) error_log( "Illegal character provided in handler \"" . $_POST['handler'] . "\"." );
|
||||
json_reply( array( 'error' => "Bad handler provided." ) );
|
||||
exit( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
if( DEBUG ) error_log( "handler is \"$handler\"." );
|
||||
|
||||
if( $handler == 'autoconfig_save' )
|
||||
{
|
||||
if( DEBUG ) error_log( "Got here saving configuration." );
|
||||
if( !( $form = $ah->save_config( $_POST ) ) )
|
||||
{
|
||||
if( DEBUG ) error_log( "Failed to save config: " . $ah->error_as_string() );
|
||||
json_reply( array( 'error' => sprintf( $PALANG['pAutoconfig_server_side_error'], $ah->error_as_string() ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) error_log( "Ok, config saved." );
|
||||
// We return the newly created ids so the user can perform a follow-on update
|
||||
// The Ajax script will take care of setting those values in the hidden fields
|
||||
json_reply( array(
|
||||
'success' => $PALANG['pAutoconfig_config_saved'],
|
||||
'config_id' => $form['config_id'],
|
||||
'incoming_server' => $form['incoming_server'],
|
||||
'outgoing_server' => $form['outgoing_server'],
|
||||
'instruction' => $form['enable']['instruction'],
|
||||
'documentation' => $form['documentation']['description'],
|
||||
) );
|
||||
}
|
||||
}
|
||||
elseif( $handler == 'autoconfig_remove' )
|
||||
{
|
||||
if( DEBUG ) error_log( "Got here removing configuration id " . $_POST['config_id'] );
|
||||
if( empty( $_POST['config_id'] ) )
|
||||
{
|
||||
json_reply( array( 'error' => $PALANG['pAutoconfig_no_config_yet_to_remove'] ) );
|
||||
exit( 0 );
|
||||
}
|
||||
if( !$ah->remove_config( $_POST['config_id'] ) )
|
||||
{
|
||||
if( DEBUG ) error_log( "Failed to remove config: " . $ah->error_as_string() );
|
||||
json_reply( array( 'error' => sprintf( $PALANG['pAutoconfig_server_side_error'], $ah->error_as_string() ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG ) error_log( "Ok, config removed." );
|
||||
json_reply( array( 'success' => $PALANG['pAutoconfig_config_removed'] ) );
|
||||
}
|
||||
exit( 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
json_reply( array( 'error' => 'Unknown handler provided "' . $handler . '".' ) );
|
||||
}
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
function json_reply( $data )
|
||||
{
|
||||
if( !array_key_exists( 'error', $data ) &&
|
||||
!array_key_exists( 'info', $data ) &&
|
||||
!array_key_exists( 'success', $data ) )
|
||||
{
|
||||
error_log( "json_reply() missing message type: error, info or success" );
|
||||
return( false );
|
||||
}
|
||||
$allowed_domain = 'http'
|
||||
. ( ( array_key_exists( 'HTTPS', $_SERVER )
|
||||
&& $_SERVER[ 'HTTPS' ]
|
||||
&& strtolower( $_SERVER[ 'HTTPS' ] ) !== 'off' )
|
||||
? 's'
|
||||
: null )
|
||||
. '://' . $_SERVER[ 'HTTP_HOST' ];
|
||||
header( "Access-Control-Allow-Origin: $allowed_domain" );
|
||||
header( 'Content-Type: application/json;charset=utf-8' );
|
||||
if( DEBUG ) error_log( "Returning to client the payload: " . json_encode( $data ) );
|
||||
echo json_encode( $data );
|
||||
return( true );
|
||||
}
|
||||
|
||||
function showAutoconfigForm( &$form )
|
||||
{
|
||||
global $PALANG, $CONF, $languages, $smarty;
|
||||
if( DEBUG ) error_log( "showAutoconfigForm() received form data: " + print_r( $form, true ) );
|
||||
if( $form == null ) $form = array();
|
||||
if( array_key_exists( 'enable', $form ) )
|
||||
{
|
||||
if( array_key_exists( 'instruction', $form['enable'] ) )
|
||||
{
|
||||
if( count( $form['enable']['instruction'] ) == 0 )
|
||||
{
|
||||
$form['enable']['instruction'][] = array( 'lang' => 'en' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( array_key_exists( 'documentation', $form ) )
|
||||
{
|
||||
if( array_key_exists( 'description', $form['documentation'] ) )
|
||||
{
|
||||
if( count( $form['documentation']['description'] ) == 0 )
|
||||
{
|
||||
$form['documentation']['description'][] = array( 'lang' => 'en' );
|
||||
}
|
||||
}
|
||||
}
|
||||
$smarty->assign( 'form', $form );
|
||||
$smarty->assign( 'language_options', $languages );
|
||||
$smarty->assign( 'default_lang', 'en' );
|
||||
$smarty->assign( 'smarty_template', 'autoconfig' );
|
||||
$smarty->display( 'index.tpl');
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/* vim: set expandtab softtabstop=3 tabstop=3 shiftwidth=3: */
|
||||
|
||||
?>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,302 @@
|
||||
{literal}
|
||||
<link rel="stylesheet" type="text/css" href="autoconfig.css" />
|
||||
<script language="JavaScript" src="https://code.jquery.com/jquery-3.3.1.min.js" type="text/javascript"></script>
|
||||
<script language="JavaScript" src="sprintf.js" type="text/javascript"></script>
|
||||
<script language="JavaScript" src="autoconfig.js" type="text/javascript"></script>
|
||||
{/literal}
|
||||
<script id="host-template" type="text/template">
|
||||
{include file='autoconfig-host-settings.tpl'}
|
||||
</script>
|
||||
<div id="postfixadmin-progress" class="waiting">
|
||||
<dt></dt>
|
||||
<dd></dd>
|
||||
</div>
|
||||
<div id="message"></div>
|
||||
<div id="edit_form">
|
||||
<form id="autoconfig_form" name="edit-autoconfig" method="post" action=''>
|
||||
<input class="flat" type="hidden" name="token" value="{$smarty.session.PFA_token|escape:"url"}" />
|
||||
<input class="flat" type="hidden" name="config_id" value="{$form.config_id}" />
|
||||
<input type="checkbox" class="switch-input" name="enable_status" id="enable_status" value="1" {if !empty($form.enable_status) && $form.enable_status == 1}checked="checked"{/if} />
|
||||
<input type="checkbox" class="switch-input" name="documentation_status" id="documentation_status" value="1" {if !empty($form.documentation_status) && $form.documentation_status == 1}checked="checked"{/if} />
|
||||
<datalist id="defaultPorts">
|
||||
<option value="25">
|
||||
<option value="143">
|
||||
<option value="465">
|
||||
<option value="587">
|
||||
<option value="993">
|
||||
<option value="995">
|
||||
<option value="2525">
|
||||
</datalist>
|
||||
<table>
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_page_title}</th>
|
||||
</tr>
|
||||
<tr class="autoconfig_jump">
|
||||
<td class="label"><label for="autoconfig_active">{$PALANG.pAutoconfig_jump_to}:</label></td>
|
||||
<td><select name="jump_to">
|
||||
<option value="">{$PALANG.pAutoconfig_new_configuration}</option>
|
||||
{html_options options=$form.config_options selected=$form.config_id}
|
||||
</select></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label for="autoconfig_active">{$PALANG.pAutoconfig_active}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="active" id="autoconfig_active" value="1" {if !empty($form.active) && $form.active == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_encoding}:</label></td>
|
||||
<td><input type="hidden" class="flat" name="encoding" value="utf-8" /><em>utf-8</em></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_provider_id}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="provider_id" maxlength="255" value="{$form.provider_id}" placeholder="{$form.placeholder.provider_id}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_provider_domain}:</label></td>
|
||||
<td><select class="flat" name="provider_domain[]" id="autoconfig_provider_domain" size="10" multiple="multiple">
|
||||
{foreach from=$form.provider_domain_options item=domain}
|
||||
<option value="{$domain}"{if in_array($domain, $form.provider_domain)} selected="selected"{elseif in_array($domain, $form.provider_domain_disabled)} disabled="true"{/if}>{$domain}</option>
|
||||
{/foreach}
|
||||
</select><br /><input type="button" id="autoconfig_toggle_select_all_domains" value="{$PALANG.pAutoconfig_toggle_select_all}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_provider_name}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="provider_name" maxlength="255" value="{$form.provider_name}" placeholder="{$form.placeholder.provider_name}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_provider_short}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="provider_short" maxlength="120" value="{$form.provider_short}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<!-- looping for each incoming server -->
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_incoming_server}</th>
|
||||
</tr>
|
||||
{if count($form.incoming_server) == 0}
|
||||
{assign var="server" value=['type' => 'imap'] scope="global"}
|
||||
{include file='autoconfig-host-settings.tpl' server=$server}
|
||||
{else}
|
||||
{foreach name=outer item=server from=$form.incoming_server}
|
||||
{include file='autoconfig-host-settings.tpl' server=$server}
|
||||
{/foreach}
|
||||
{/if}
|
||||
<!-- end looping for each incoming server -->
|
||||
<!-- looping for each outgoing server -->
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_outgoing_server}</th>
|
||||
</tr>
|
||||
{if count($form.outgoing_server) == 0}
|
||||
{assign var="server" value=['type' => 'smtp'] scope="global"}
|
||||
{include file='autoconfig-host-settings.tpl' server=$server}
|
||||
{else}
|
||||
{foreach name=outer item=server from=$form.outgoing_server}
|
||||
{include file='autoconfig-host-settings.tpl' server=$server}
|
||||
{/foreach}
|
||||
{/if}
|
||||
<!-- end looping for each outgoing server -->
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_enable}
|
||||
<label class="switch" for="enable_status">
|
||||
<span class="switch-label" data-on="{$PALANG.pAutoconfig_on}" data-off="{$PALANG.pAutoconfig_off}"></span>
|
||||
<span class="switch-handle"></span>
|
||||
</label>
|
||||
<br />{$PALANG.pAutoconfig_not_supported} (<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration/FileFormat/HowTo" title="Autoconfig: How to create a configuration file" target="_new">?</a>)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_enable_url}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="enable_url" maxlength="2048" value="{$form.enable.url}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{foreach name=support item=text from=$form.enable.instruction}
|
||||
<tr class="autoconfig-instruction">
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_enable_instruction}:</label></td>
|
||||
<td><input type="hidden" name="instruction_id[]" value="{$text.id}" />
|
||||
{html_options name="instruction_lang[]" options=$language_options selected=$text.lang}<br />
|
||||
<textarea class="flat" rows="5" cols="50" name="instruction_text[]" >{$text.phrase}</textarea></td>
|
||||
<td><button class="autoconfig-command autoconfig-instruction autoconfig-locale-text-add ripple" title="{$PALANG.pAutoconfig_add_new_text}"><i class="fas fa-plus fa-2x"></i></button><button class="autoconfig-command autoconfig-instruction autoconfig-locale-text-remove ripple" title="{$PALANG.pAutoconfig_remove_text}"><i class="fas fa-minus fa-2x"></i></button></td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_documentation}
|
||||
<label class="switch" for="documentation_status">
|
||||
<span class="switch-label" data-on="{$PALANG.pAutoconfig_on}" data-off="{$PALANG.pAutoconfig_off}"></span>
|
||||
<span class="switch-handle"></span>
|
||||
</label>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_documentation_url}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="documentation_url" maxlength="2048" value="{$form.documentation.url}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{foreach name=support item=text from=$form.documentation.description}
|
||||
<tr class="autoconfig-documentation">
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_documentation_desc}:</label></td>
|
||||
<td><input type="hidden" name="documentation_id[]" value="{$text.id}" />
|
||||
{html_options name="documentation_lang[]" options=$language_options selected=$text.lang}<br />
|
||||
<textarea class="flat" rows="5" cols="50" name="documentation_text[]" >{$text.phrase}</textarea></td>
|
||||
<td><button class="autoconfig-command autoconfig-documentation autoconfig-locale-text-add ripple" title="{$PALANG.pAutoconfig_add_new_text}"><i class="fas fa-plus fa-2x"></i></button><button class="autoconfig-command autoconfig-documentation autoconfig-locale-text-remove ripple" title="{$PALANG.pAutoconfig_remove_text}"><i class="fas fa-minus fa-2x"></i></button></td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_webmail}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_webmail_login_page}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="webmail_login_page" maxlength="2048" value="{$form.webmail_login_page}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_webmail_login_page_info}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_lp_info_url}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="lp_info_url" maxlength="2048" value="{$form.lp_info_url}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_lp_info_username}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_lp_info_username_field_id}:</label></td>
|
||||
<td><input type="text" class="flat" name="lp_info_username_field_id" maxlength="255" value="{$form.lp_info_username_field_id}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_lp_info_username_field_name}:</label></td>
|
||||
<td><input type="text" class="flat" name="lp_info_username_field_name" maxlength="255" value="{$form.lp_info_username_field_name}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_lp_info_login_button}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_lp_info_login_button_id}:</label></td>
|
||||
<td><input type="text" class="flat" name="lp_info_login_button_id" maxlength="255" value="{$form.lp_info_login_button_id}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_lp_info_login_button_name}:</label></td>
|
||||
<td><input type="text" class="flat" name="lp_info_login_button_name" maxlength="255" value="{$form.lp_info_login_button_name}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_mac_specific_settings}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_account_name}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="account_name" maxlength="255" value="{$form.account_name}" placeholder="John Doe" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_account_type}:</label></td>
|
||||
<td><select name="account_type">
|
||||
<option value="imap" {if !empty($form.account_type) && $form.account_type == "imap"}selected{/if}>imap</option>
|
||||
<option value="pop3" {if !empty($form.account_type) && $form.account_type == "pop3"}selected{/if}>pop3</option>
|
||||
</select></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_email}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="email" maxlength="255" value="{$form.email}" placeholder="john.doe@example.org" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<!-- Not sure this field should stay there, because it is automatically computed based on the sock_type, ie SSL, TLS, STARTTLS -->
|
||||
<tr>
|
||||
<td class="label"><label for="autoconfig_ssl">{$PALANG.pAutoconfig_ssl}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="ssl_enabled" id="autoconfig_ssl" value="1" {if !empty($form.ssl_enabled) && $form.ssl_enabled == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<!--
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.Autoconfig_password}:</label></td>
|
||||
<td><input type="text" class="flat" name="password" maxlength="255" value="{$form.password}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
-->
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_description}:</label></td>
|
||||
<td><textarea class="flat" rows="5" cols="60" name="description" >{$form.description}</textarea></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_organisation}:</label></td>
|
||||
<td><input type="text" size="40" class="flat" name="organisation" maxlength="255" value="{$form.organisation}" /><input type="button" id="copy_provider_value" value="{$PALANG.pAutoconfig_copy_provider_name}" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_payload_type}:</label></td>
|
||||
<td><select name="payload_type">
|
||||
<option value="com.apple.mail.managed" {if !empty($form.payload_type) && $form.payload_type == "com.apple.mail.managed"}selected{/if}>Mail account</option>
|
||||
<option value="com.apple.eas.account" {if !empty($form.payload_type) && $form.payload_type == "com.apple.eas.account"}selected{/if}>Microsoft Exchange</option>
|
||||
</select></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label for="autoconfig_prevent_app_sheet">{$PALANG.pAutoconfig_prevent_app_sheet}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="prevent_app_sheet" id="autoconfig_prevent_app_sheet" value="1" {if !empty($form.prevent_app_sheet) && $form.prevent_app_sheet == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label for="autoconfig_prevent_move">{$PALANG.pAutoconfig_prevent_move}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="prevent_move" id="autoconfig_prevent_move" value="1" {if !empty($form.prevent_move) && $form.prevent_move == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label for="autoconfig_smime_enabled" >{$PALANG.pAutoconfig_smime_enabled}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="smime_enabled" id="autoconfig_smime_enabled" value="1" {if !empty($form.smime_enabled) && $form.smime_enabled == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label for="autoconfig_payload_remove_ok">{$PALANG.pAutoconfig_payload_remove_ok}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="payload_remove_ok" id="autoconfig_payload_remove_ok" value="1" {if !empty($form.payload_remove_ok) && $form.payload_remove_ok == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label for="autoconfig_spa">{$PALANG.pAutoconfig_spa}:</label></td>
|
||||
<td><input type="checkbox" class="flat" name="spa" id="autoconfig_spa" value="1" {if !empty($form.spa) && $form.spa == 1}checked="checked"{/if} /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3">{$PALANG.pAutoconfig_cert_sign}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_cert_option}:</label></td>
|
||||
<td><select name="sign_option">
|
||||
<option value="none" {if !empty($form.sign_option) && $form.sign_option == "none"}selected{/if}>{$PALANG.pAutoconfig_cert_none}</option>
|
||||
<option value="local" {if !empty($form.sign_option) && $form.sign_option == "local"}selected{/if}>{$PALANG.pAutoconfig_cert_local}</option>
|
||||
<option value="global" {if !empty($form.sign_option) && $form.sign_option == "global"}selected{/if}>{$PALANG.pAutoconfig_cert_global}</option>
|
||||
</select></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr class="cert_files">
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_cert_filepath}:</label></td>
|
||||
<td><input type="text" size="70" class="flat" name="cert_filepath" maxlength="1024" value="{$form.cert_filepath}" placeholder="/etc/letsencrypt/live/example.com/cert.pm" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr class="cert_files">
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_privkey_filepath}:</label></td>
|
||||
<td><input type="text" size="70" class="flat" name="privkey_filepath" maxlength="1024" value="{$form.privkey_filepath}" placeholder="/etc/letsencrypt/live/example.com/privkey.pm" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr class="cert_files">
|
||||
<td class="label"><label>{$PALANG.pAutoconfig_chain_filepath}:</label></td>
|
||||
<td><input type="text" size="70" class="flat" name="chain_filepath" maxlength="1024" value="{$form.chain_filepath}" placeholder="/etc/letsencrypt/live/example.com/chain.pm" /></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td colspan="2">
|
||||
<input class="button" id="autoconfig_save" type="submit" name="fChange" value="{$PALANG.pEdit_autoconfig_set}" />
|
||||
<input class="button" id="autoconfig_remove" type="submit" name="fBack" value="{$PALANG.pEdit_autoconfig_remove}" />
|
||||
<input class="button" id="autoconfig_cancel" type="submit" name="fCancel" value="{$PALANG.exit}" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/*
|
||||
Created on 2020-03-10
|
||||
Copyright 2020 Jacques Deguest
|
||||
Distributed under the same licence as Postfix Admin
|
||||
*/
|
||||
$languages = array(
|
||||
'ab' => "Abkhaz",
|
||||
'aa' => "Afar",
|
||||
'af' => "Afrikaans",
|
||||
'ak' => "Akan",
|
||||
'sq' => "Albanian",
|
||||
'am' => "Amharic",
|
||||
'ar' => "Arabic",
|
||||
'an' => "Aragonese",
|
||||
'hy' => "Armenian",
|
||||
'as' => "Assamese",
|
||||
'av' => "Avaric",
|
||||
'ae' => "Avestan",
|
||||
'ay' => "Aymara",
|
||||
'az' => "Azerbaijani",
|
||||
'bm' => "Bambara",
|
||||
'ba' => "Bashkir",
|
||||
'eu' => "Basque",
|
||||
'be' => "Belarusian",
|
||||
'bn' => "Bengali, Bangla",
|
||||
'bh' => "Bihari",
|
||||
'bi' => "Bislama",
|
||||
'bs' => "Bosnian",
|
||||
'br' => "Breton",
|
||||
'bg' => "Bulgarian",
|
||||
'my' => "Burmese",
|
||||
'ca' => "Catalan",
|
||||
'ch' => "Chamorro",
|
||||
'ce' => "Chechen",
|
||||
'ny' => "Chichewa, Chewa, Nyanja",
|
||||
'zh' => "Chinese",
|
||||
'cv' => "Chuvash",
|
||||
'kw' => "Cornish",
|
||||
'co' => "Corsican",
|
||||
'cr' => "Cree",
|
||||
'hr' => "Croatian",
|
||||
'cs' => "Czech",
|
||||
'da' => "Danish",
|
||||
'dv' => "Divehi, Dhivehi, Maldivian",
|
||||
'nl' => "Dutch",
|
||||
'dz' => "Dzongkha",
|
||||
'en' => "English",
|
||||
'eo' => "Esperanto",
|
||||
'et' => "Estonian",
|
||||
'ee' => "Ewe",
|
||||
'fo' => "Faroese",
|
||||
'fj' => "Fijian",
|
||||
'fi' => "Finnish",
|
||||
'fr' => "French",
|
||||
'ff' => "Fula, Fulah, Pulaar, Pular",
|
||||
'gl' => "Galician",
|
||||
'lg' => "Ganda",
|
||||
'ka' => "Georgian",
|
||||
'de' => "German",
|
||||
'el' => "Greek (modern)",
|
||||
'gn' => "Guaraní",
|
||||
'gu' => "Gujarati",
|
||||
'ht' => "Haitian, Haitian Creole",
|
||||
'ha' => "Hausa",
|
||||
'he' => "Hebrew (modern)",
|
||||
'hz' => "Herero",
|
||||
'hi' => "Hindi",
|
||||
'ho' => "Hiri Motu",
|
||||
'hu' => "Hungarian",
|
||||
'is' => "Icelandic",
|
||||
'io' => "Ido",
|
||||
'ig' => "Igbo",
|
||||
'id' => "Indonesian",
|
||||
'ia' => "Interlingua",
|
||||
'ie' => "Interlingue",
|
||||
'iu' => "Inuktitut",
|
||||
'ik' => "Inupiaq",
|
||||
'ga' => "Irish",
|
||||
'it' => "Italian",
|
||||
'ja' => "Japanese",
|
||||
'jv' => "Javanese",
|
||||
'kl' => "Kalaallisut, Greenlandic",
|
||||
'kn' => "Kannada",
|
||||
'kr' => "Kanuri",
|
||||
'ks' => "Kashmiri",
|
||||
'kk' => "Kazakh",
|
||||
'km' => "Khmer",
|
||||
'ki' => "Kikuyu, Gikuyu",
|
||||
'rw' => "Kinyarwanda",
|
||||
'rn' => "Kirundi",
|
||||
'kv' => "Komi",
|
||||
'kg' => "Kongo",
|
||||
'ko' => "Korean",
|
||||
'ku' => "Kurdish",
|
||||
'kj' => "Kwanyama, Kuanyama",
|
||||
'ky' => "Kyrgyz",
|
||||
'lo' => "Lao",
|
||||
'la' => "Latin",
|
||||
'lv' => "Latvian",
|
||||
'li' => "Limburgish, Limburgan, Limburger",
|
||||
'ln' => "Lingala",
|
||||
'lt' => "Lithuanian",
|
||||
'lu' => "Luba-Katanga",
|
||||
'lb' => "Luxembourgish, Letzeburgesch",
|
||||
'mk' => "Macedonian",
|
||||
'mg' => "Malagasy",
|
||||
'ms' => "Malay",
|
||||
'ml' => "Malayalam",
|
||||
'mt' => "Maltese",
|
||||
'gv' => "Manx",
|
||||
'mr' => "Marathi (Marāṭhī)",
|
||||
'mh' => "Marshallese",
|
||||
'mn' => "Mongolian",
|
||||
'mi' => "Māori",
|
||||
'na' => "Nauruan",
|
||||
'nv' => "Navajo, Navaho",
|
||||
'ng' => "Ndonga",
|
||||
'ne' => "Nepali",
|
||||
'nd' => "Northern Ndebele",
|
||||
'se' => "Northern Sami",
|
||||
'no' => "Norwegian",
|
||||
'nb' => "Norwegian Bokmål",
|
||||
'nn' => "Norwegian Nynorsk",
|
||||
'ii' => "Nuosu",
|
||||
'oc' => "Occitan",
|
||||
'oj' => "Ojibwe, Ojibwa",
|
||||
'cu' => "Old Church Slavonic, Church Slavonic, Old Bulgarian",
|
||||
'or' => "Oriya",
|
||||
'om' => "Oromo",
|
||||
'os' => "Ossetian, Ossetic",
|
||||
'pa' => "Panjabi, Punjabi",
|
||||
'ps' => "Pashto, Pushto",
|
||||
'fa' => "Persian (Farsi)",
|
||||
'pl' => "Polish",
|
||||
'pt' => "Portuguese",
|
||||
'pi' => "Pāli",
|
||||
'qu' => "Quechua",
|
||||
'ro' => "Romanian",
|
||||
'rm' => "Romansh",
|
||||
'ru' => "Russian",
|
||||
'sm' => "Samoan",
|
||||
'sg' => "Sango",
|
||||
'sa' => "Sanskrit (Saṁskṛta)",
|
||||
'sc' => "Sardinian",
|
||||
'gd' => "Scottish Gaelic, Gaelic",
|
||||
'sr' => "Serbian",
|
||||
'sn' => "Shona",
|
||||
'sd' => "Sindhi",
|
||||
'si' => "Sinhala, Sinhalese",
|
||||
'sk' => "Slovak",
|
||||
'sl' => "Slovene",
|
||||
'so' => "Somali",
|
||||
'nr' => "Southern Ndebele",
|
||||
'st' => "Southern Sotho",
|
||||
'es' => "Spanish",
|
||||
'su' => "Sundanese",
|
||||
'sw' => "Swahili",
|
||||
'ss' => "Swati",
|
||||
'sv' => "Swedish",
|
||||
'tl' => "Tagalog",
|
||||
'ty' => "Tahitian",
|
||||
'tg' => "Tajik",
|
||||
'ta' => "Tamil",
|
||||
'tt' => "Tatar",
|
||||
'te' => "Telugu",
|
||||
'th' => "Thai",
|
||||
'bo' => "Tibetan Standard, Tibetan, Central",
|
||||
'ti' => "Tigrinya",
|
||||
'to' => "Tonga (Tonga Islands)",
|
||||
'ts' => "Tsonga",
|
||||
'tn' => "Tswana",
|
||||
'tr' => "Turkish",
|
||||
'tk' => "Turkmen",
|
||||
'tw' => "Twi",
|
||||
'uk' => "Ukrainian",
|
||||
'ur' => "Urdu",
|
||||
'ug' => "Uyghur",
|
||||
'uz' => "Uzbek",
|
||||
've' => "Venda",
|
||||
'vi' => "Vietnamese",
|
||||
'vo' => "Volapük",
|
||||
'wa' => "Walloon",
|
||||
'cy' => "Welsh",
|
||||
'fy' => "Western Frisian",
|
||||
'wo' => "Wolof",
|
||||
'xh' => "Xhosa",
|
||||
'yi' => "Yiddish",
|
||||
'yo' => "Yoruba",
|
||||
'za' => "Zhuang, Chuang",
|
||||
'zu' => "Zulu",
|
||||
);
|
||||
?>
|
@ -0,0 +1,212 @@
|
||||
/**
|
||||
* JavaScript printf/sprintf functions.
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
*
|
||||
* This code is unrestricted: you are free to use it however you like.
|
||||
*
|
||||
* The functions should work as expected, performing left or right alignment,
|
||||
* truncating strings, outputting numbers with a required precision etc.
|
||||
*
|
||||
* For complex cases these functions follow the Perl implementations of
|
||||
* (s)printf, allowing arguments to be passed out-of-order, and to set
|
||||
* precision and output-length from other argument
|
||||
*
|
||||
* See http://perldoc.perl.org/functions/sprintf.html for more information.
|
||||
*
|
||||
* Implemented flags:
|
||||
*
|
||||
* - zero or space-padding (default: space)
|
||||
* sprintf("%4d", 3) -> " 3"
|
||||
* sprintf("%04d", 3) -> "0003"
|
||||
*
|
||||
* - left and right-alignment (default: right)
|
||||
* sprintf("%3s", "a") -> " a"
|
||||
* sprintf("%-3s", "b") -> "b "
|
||||
*
|
||||
* - out of order arguments (good for templates & message formats)
|
||||
* sprintf("Estimate: %2$d units total: %1$.2f total", total, quantity)
|
||||
*
|
||||
* - binary, octal and hex prefixes (default: none)
|
||||
* sprintf("%b", 13) -> "1101"
|
||||
* sprintf("%#b", 13) -> "0b1101"
|
||||
* sprintf("%#06x", 13) -> "0x000d"
|
||||
*
|
||||
* - positive number prefix (default: none)
|
||||
* sprintf("%d", 3) -> "3"
|
||||
* sprintf("%+d", 3) -> "+3"
|
||||
* sprintf("% d", 3) -> " 3"
|
||||
*
|
||||
* - min/max width (with truncation); e.g. "%9.3s" and "%-9.3s"
|
||||
* sprintf("%5s", "catfish") -> "catfish"
|
||||
* sprintf("%.5s", "catfish") -> "catfi"
|
||||
* sprintf("%5.3s", "catfish") -> " cat"
|
||||
* sprintf("%-5.3s", "catfish") -> "cat "
|
||||
*
|
||||
* - precision (see note below); e.g. "%.2f"
|
||||
* sprintf("%.3f", 2.1) -> "2.100"
|
||||
* sprintf("%.3e", 2.1) -> "2.100e+0"
|
||||
* sprintf("%.3g", 2.1) -> "2.10"
|
||||
* sprintf("%.3p", 2.1) -> "2.1"
|
||||
* sprintf("%.3p", '2.100') -> "2.10"
|
||||
*
|
||||
* Deviations from perl spec:
|
||||
* - %n suppresses an argument
|
||||
* - %p and %P act like %g, but without over-claiming accuracy:
|
||||
* Compare:
|
||||
* sprintf("%.3g", "2.1") -> "2.10"
|
||||
* sprintf("%.3p", "2.1") -> "2.1"
|
||||
*
|
||||
* @version 2011.09.23
|
||||
* @author Ash Searle
|
||||
*/
|
||||
function sprintf() {
|
||||
function pad(str, len, chr, leftJustify) {
|
||||
var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
|
||||
return leftJustify ? str + padding : padding + str;
|
||||
|
||||
}
|
||||
|
||||
function justify(value, prefix, leftJustify, minWidth, zeroPad) {
|
||||
var diff = minWidth - value.length;
|
||||
if (diff > 0) {
|
||||
if (leftJustify || !zeroPad) {
|
||||
value = pad(value, minWidth, ' ', leftJustify);
|
||||
} else {
|
||||
value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
var a = arguments, i = 0, format = a[i++];
|
||||
return format.replace(sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) {
|
||||
if (substring == '%%') return '%';
|
||||
|
||||
// parse flags
|
||||
var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false;
|
||||
for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) {
|
||||
case ' ': positivePrefix = ' '; break;
|
||||
case '+': positivePrefix = '+'; break;
|
||||
case '-': leftJustify = true; break;
|
||||
case '0': zeroPad = true; break;
|
||||
case '#': prefixBaseX = true; break;
|
||||
}
|
||||
|
||||
// parameters may be null, undefined, empty-string or real valued
|
||||
// we want to ignore null, undefined and empty-string values
|
||||
|
||||
if (!minWidth) {
|
||||
minWidth = 0;
|
||||
} else if (minWidth == '*') {
|
||||
minWidth = +a[i++];
|
||||
} else if (minWidth.charAt(0) == '*') {
|
||||
minWidth = +a[minWidth.slice(1, -1)];
|
||||
} else {
|
||||
minWidth = +minWidth;
|
||||
}
|
||||
|
||||
// Note: undocumented perl feature:
|
||||
if (minWidth < 0) {
|
||||
minWidth = -minWidth;
|
||||
leftJustify = true;
|
||||
}
|
||||
|
||||
if (!isFinite(minWidth)) {
|
||||
throw new Error('sprintf: (minimum-)width must be finite');
|
||||
}
|
||||
|
||||
if (precision && precision.charAt(0) == '*') {
|
||||
precision = +a[(precision == '*') ? i++ : precision.slice(1, -1)];
|
||||
if (precision < 0) {
|
||||
precision = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (precision == null) {
|
||||
precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
|
||||
} else {
|
||||
precision = +precision;
|
||||
}
|
||||
|
||||
// grab value using valueIndex if required?
|
||||
var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
|
||||
var prefix, base;
|
||||
|
||||
switch (type) {
|
||||
case 'c': value = String.fromCharCode(+value);
|
||||
case 's': {
|
||||
// If you'd rather treat nulls as empty-strings, uncomment next line:
|
||||
// if (value == null) return '';
|
||||
|
||||
value = String(value);
|
||||
if (precision != null) {
|
||||
value = value.slice(0, precision);
|
||||
}
|
||||
prefix = '';
|
||||
break;
|
||||
}
|
||||
case 'b': base = 2; break;
|
||||
case 'o': base = 8; break;
|
||||
case 'u': base = 10; break;
|
||||
case 'x': case 'X': base = 16; break;
|
||||
case 'i':
|
||||
case 'd': {
|
||||
var number = parseInt(+value);
|
||||
if (isNaN(number)) {
|
||||
return '';
|
||||
}
|
||||
prefix = number < 0 ? '-' : positivePrefix;
|
||||
value = prefix + pad(String(Math.abs(number)), precision, '0', false);
|
||||
break;
|
||||
}
|
||||
case 'e': case 'E':
|
||||
case 'f': case 'F':
|
||||
case 'g': case 'G':
|
||||
case 'p': case 'P':
|
||||
{
|
||||
var number = +value;
|
||||
if (isNaN(number)) {
|
||||
return '';
|
||||
}
|
||||
prefix = number < 0 ? '-' : positivePrefix;
|
||||
var method;
|
||||
if ('p' != type.toLowerCase()) {
|
||||
method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
|
||||
} else {
|
||||
// Count significant-figures, taking special-care of zeroes ('0' vs '0.00' etc.)
|
||||
var sf = String(value).replace(/[eE].*|[^\d]/g, '');
|
||||
sf = (number ? sf.replace(/^0+/,'') : sf).length;
|
||||
precision = precision ? Math.min(precision, sf) : precision;
|
||||
method = (!precision || precision <= sf) ? 'toPrecision' : 'toExponential';
|
||||
}
|
||||
var number_str = Math.abs(number)[method](precision);
|
||||
// number_str = thousandSeparation ? thousand_separate(number_str): number_str;
|
||||
value = prefix + number_str;
|
||||
break;
|
||||
}
|
||||
case 'n': return '';
|
||||
default: return substring;
|
||||
}
|
||||
|
||||
if (base) {
|
||||
// cast to non-negative integer:
|
||||
var number = value >>> 0;
|
||||
prefix = prefixBaseX && base != 10 && number && ['0b', '0', '0x'][base >> 3] || '';
|
||||
value = prefix + pad(number.toString(base), precision || 0, '0', false);
|
||||
}
|
||||
var justified = justify(value, prefix, leftJustify, minWidth, zeroPad);
|
||||
return ('EFGPX'.indexOf(type) > -1) ? justified.toUpperCase() : justified;
|
||||
});
|
||||
}
|
||||
sprintf.regex = /%%|%(\d+\$)?([-+#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegpEGP])/g;
|
||||
|
||||
/**
|
||||
* Trival printf implementation, probably only useful during page-load.
|
||||
* Note: you may as well use "document.write(sprintf(....))" directly
|
||||
*/
|
||||
function printf() {
|
||||
// delegate the work to sprintf in an IE5 friendly manner:
|
||||
var i = 0, a = arguments, args = Array(arguments.length);
|
||||
while (i < args.length) args[i] = 'a[' + (i++) + ']';
|
||||
document.write(eval('sprintf(' + args + ')'));
|
||||
}
|
Loading…
Reference in New Issue